<?php

namespace BookWellAB;

defined('ABSPATH') || exit;

if (!class_exists('BookWellAB_Input')) {

    /**
     * Centralized POST input helpers for BookWellNow.
     *
     * Security model (WordPress: sanitize early, escape late):
     * - AJAX handlers MUST verify nonce and capability before calling these methods.
     * - get_post_string() and get_post_json() return unslashed data (JSON is decoded only).
     * - Every caller MUST sanitize each field before database insert/update or output.
     * - get_post_array() applies sanitize_text_field to all scalar values via map_deep().
     * - decode_json_or_original() is for stored DB values only, not live POST.
     * - Output to admin/frontend uses esc_* or Vue binding; decode_entities_for_display is read-path only.
     */
    class BookWellAB_Input
    {
        /**
         * @param string $key POST key.
         * @return string Sanitized key.
         */
        private static function sanitize_post_key($key)
        {
            return sanitize_key((string) $key);
        }

        /**
         * Unslashed and sanitized POST string.
         *
         * @param string $key POST key.
         * @return string
         */
        public static function get_post_string($key)
        {
            $key = self::sanitize_post_key($key);

            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by AJAX handler before calling helpers.
            if (!isset($_POST[$key]) || !is_string($_POST[$key])) {
                return '';
            }

            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by AJAX handler.
            return sanitize_text_field(wp_unslash($_POST[$key]));
        }

        /**
         * POST array with all scalar values passed through sanitize_text_field (map_deep).
         *
         * @param string $key POST key.
         * @return array
         */
        public static function get_post_array($key)
        {
            $key = self::sanitize_post_key($key);

            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by AJAX handler before calling helpers.
            if (!isset($_POST[$key]) || !is_array($_POST[$key])) {
                return array();
            }

            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by AJAX handler.
            return map_deep(wp_unslash($_POST[$key]), 'sanitize_text_field');
        }

        /**
         * Decoded JSON array from POST. Caller must run sanitize_*_payload() or per-field sanitizers before save.
         *
         * @param string $key POST key.
         * @return array
         */
        public static function get_post_json($key)
        {
            $key = self::sanitize_post_key($key);

            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by AJAX handler before calling helpers.
            if (!isset($_POST[$key]) || !is_string($_POST[$key])) {
                return array();
            }

            // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Raw JSON string is decoded first, and then each decoded value is sanitized recursively or field-by-field before database save.
            $raw = wp_unslash($_POST[$key]);
            $decoded = json_decode($raw, true);

            return is_array($decoded) ? $decoded : array();
        }

        /**
         * Decode a JSON string into an array.
         *
         * @param string $raw JSON string.
         * @return array
         */
        public static function decode_json($raw)
        {
            if (!is_string($raw) || $raw === '') {
                return array();
            }

            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Caller sanitizes each decoded field before use.
            $decoded = json_decode($raw, true);

            return is_array($decoded) ? $decoded : array();
        }

        /**
         * Decode JSON from storage; return original string when JSON is invalid.
         *
         * @param string $raw Raw stored value.
         * @return mixed
         */
        public static function decode_json_or_original($raw)
        {
            if (!is_string($raw) || $raw === '') {
                return $raw;
            }

            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Stored settings are sanitized before save; scalar fallback returned as-is.
            $decoded = json_decode($raw, true);

            if (JSON_ERROR_NONE !== json_last_error()) {
                return $raw;
            }

            return $decoded;
        }

        /**
         * Decode break-hours JSON and sanitize each start/end time.
         *
         * @param mixed $breaks JSON string or array of break items.
         * @return array<int, array{start: string, end: string}>
         */
        public static function sanitize_break_hours_list($breaks)
        {
            $decoded = is_array($breaks) ? $breaks : self::decode_json((string) $breaks);
            $sanitized = array();

            foreach ($decoded as $break_item) {
                if (!is_array($break_item)) {
                    continue;
                }

                $start = isset($break_item['start']) ? sanitize_text_field((string) $break_item['start']) : '';
                $end = isset($break_item['end']) ? sanitize_text_field((string) $break_item['end']) : '';

                if ($start !== '' && $end !== '') {
                    $sanitized[] = array(
                        'start' => $start,
                        'end' => $end,
                    );
                }
            }

            return $sanitized;
        }

        /**
         * Normalize an intl-tel-input ISO2 country slug.
         *
         * @param mixed $slug Raw country slug.
         * @return string Lowercase ISO2 code or empty string.
         */
        public static function sanitize_country_slug($slug)
        {
            $slug = strtolower(sanitize_text_field((string) $slug));

            if ($slug === '' || !preg_match('/^[a-z]{2}$/', $slug)) {
                return '';
            }

            return $slug;
        }

        /**
         * Decode HTML entities for admin/Vue JSON payloads (Vue escapes text on render).
         *
         * @param mixed $value Raw DB or legacy escaped value.
         * @return string
         */
        public static function decode_entities_for_display($value)
        {
            if (!is_string($value) || $value === '') {
                return is_string($value) ? $value : '';
            }

            $decoded = $value;
            for ($pass = 0; $pass < 3; $pass++) {
                $next = wp_specialchars_decode($decoded, ENT_QUOTES);
                if ($next === $decoded) {
                    break;
                }
                $decoded = $next;
            }

            return $decoded;
        }
    }
}
