<?php
/**
 * TODO-CLIENT-001 (2026-08-02): centralized superglobal read helper.
 *
 * WordPress 7.0+ does NOT auto-apply magic_quotes_gpc() to $_POST/$_GET/$_REQUEST.
 * WP versions 4.x-6.4 DID apply magic quotes (addslashes on all string data).
 *
 * This helper wraps every $_SUPERGLOBAL read with wp_unslash() as defense
 * in depth, so:
 *  - On WP 4.x-6.4: strips the spurious addslashes
 *  - On WP 7.0+:    no-op (data is already clean)
 *  - Tests:         always gets the un-escaped value (predictable)
 *
 * Usage:
 *   $name = Request::input('name');              // string
 *   $id   = Request::inputInt('id');             // int
 *   $ids  = Request::inputArray('ids');          // array of strings
 *   $all  = Request::inputBool('all');           // bool
 *   $val  = Request::input('opt', 'default');    // with default
 *
 * All methods:
 *  - Apply wp_unslash() automatically
 *  - Cast to the requested type
 *  - Return $default if the key is not set (or empty for required-typed)
 *  - Never throw (errors = $default value)
 *
 * @package SwapAds\Client\Http
 * @since   1.5.9
 */

declare(strict_types=1);

namespace SwapAds\Client\Http;

final class Request
{
    /**
     * Read a string value from $_POST/$_GET/$_REQUEST.
     *
     * @param string $key      The superglobal key.
     * @param string $default  Returned if the key is missing.
     * @param string $source   'post' | 'get' | 'request' (default).
     *
     * @return string Trimmed, un-escaped value.
     *
     * @since 1.5.9
     */
    public static function input(string $key, string $default = '', string $source = 'request'): string
    {
        $value = self::raw($key, $source);
        if (!is_string($value)) {
            return $default;
        }
        return (string) trim(wp_unslash($value));
    }

    /**
     * Read an integer value.
     *
     * @param string $key
     * @param int    $default
     * @param string $source
     *
     * @return int
     *
     * @since 1.5.9
     */
    public static function inputInt(string $key, int $default = 0, string $source = 'request'): int
    {
        $value = self::raw($key, $source);
        if ($value === null) {
            return $default;
        }
        return (int) $value;
    }

    /**
     * Read a boolean value.
     * Accepts: '1', 'true', 'yes', 'on' (case-insensitive) as true.
     * Everything else (including 0, '', 'false', 'no', 'off') is false.
     *
     * @param string $key
     * @param bool   $default
     * @param string $source
     *
     * @return bool
     *
     * @since 1.5.9
     */
    public static function inputBool(string $key, bool $default = false, string $source = 'request'): bool
    {
        $value = self::raw($key, $source);
        if ($value === null) {
            return $default;
        }
        if (is_bool($value)) {
            return $value;
        }
        $str = strtolower(trim((string) $value));
        return in_array($str, ['1', 'true', 'yes', 'on'], true);
    }

    /**
     * Read an array value.
     *
     * @param string $key
     * @param array<int|string, mixed> $default
     * @param string $source
     *
     * @return array<int|string, mixed> Un-escaped array (each string element
     *                                    also un-escaped). If the value is not
     *                                    an array, returns $default.
     *
     * @since 1.5.9
     */
    public static function inputArray(string $key, array $default = [], string $source = 'request'): array
    {
        $value = self::raw($key, $source);
        if (!is_array($value)) {
            return $default;
        }
        // Un-escape each string element
        return self::deepUnslash($value);
    }

    /**
     * Check if a key is set in the superglobal (regardless of value).
     *
     * @param string $key
     * @param string $source
     *
     * @return bool
     *
     * @since 1.5.9
     */
    public static function has(string $key, string $source = 'request'): bool
    {
        return self::raw($key, $source) !== null;
    }

    /**
     * Raw read from the specified superglobal, without un-escaping.
     *
     * @param string $key
     * @param string $source
     *
     * @return mixed|null  Null if the key is not set.
     */
    private static function raw(string $key, string $source)
    {
        $arr = match ($source) {
            'post'    => $_POST,
            'get'     => $_GET,
            'request' => $_REQUEST,
            default   => $_REQUEST,
        };
        return array_key_exists($key, $arr) ? $arr[$key] : null;
    }

    /**
     * Recursively un-escape a value (string or array).
     *
     * @param mixed $value
     *
     * @return mixed
     */
    private static function deepUnslash($value)
    {
        if (is_string($value)) {
            return wp_unslash($value);
        }
        if (is_array($value)) {
            return array_map([self::class, 'deepUnslash'], $value);
        }
        return $value;
    }
}
