<?php
/**
 * Settings sanitization.
 *
 * @package BatoCookieConsent
 */

if (! defined('ABSPATH')) {
    exit;
}

final class Bato_Cookie_Consent_Settings_Sanitizer
{
    public function sanitize(array $input): array
    {
        $defaults = Bato_Cookie_Consent_Settings_Defaults::defaults();
        $input = $this->merge_defaults($input);
        $language_options = array_merge(array('browser', 'auto'), Bato_Cookie_Consent_Settings_Languages::supported());

        $settings = array(
            'version' => 1,
            'updated_at' => sanitize_text_field((string) ($input['updated_at'] ?? $defaults['updated_at'])),
            'general' => array(
                'enabled' => (bool) $input['general']['enabled'],
                'mode' => $this->sanitize_choice($input['general']['mode'], array('gdpr', 'eprivacy'), 'gdpr'),
                'bannerType' => $this->sanitize_choice($input['general']['bannerType'], array('full', 'accept-only', 'no-customize', 'no-reject'), 'full'),
                'language' => $this->sanitize_choice($input['general']['language'], $language_options, Bato_Cookie_Consent_Settings_Languages::default_language()),
                'layout' => $this->sanitize_choice($input['general']['layout'], array('floating', 'bottom', 'top', 'center'), 'floating'),
                'providedBy' => $this->sanitize_formatted_text($input['general']['providedBy']),
                'logoAttachmentId' => absint($input['general']['logoAttachmentId']),
                'logoDisabled' => (bool) $input['general']['logoDisabled'],
                'useDefaultLogo' => (bool) $input['general']['useDefaultLogo'],
            ),
            'content' => $this->sanitize_content($input['content']),
            'policyLinks' => $this->sanitize_policy_links($input['policyLinks']),
            'styling' => array(
                'className' => sanitize_html_class((string) $input['styling']['className']),
            ),
            'advanced' => array(
                'expiresDays' => max(1, min(3650, absint($input['advanced']['expiresDays']))),
                'floatingButton' => array(
                    'enabled' => (bool) $input['advanced']['floatingButton']['enabled'],
                    'showAfterConsent' => (bool) $input['advanced']['floatingButton']['showAfterConsent'],
                ),
                'googleConsentMode' => (bool) $input['advanced']['googleConsentMode'],
            ),
        );

        return apply_filters('bato_cookie_consent_sanitized_settings', $settings, $input);
    }

    public function merge_defaults(array $settings): array
    {
        $settings = $this->normalize_legacy_content_keys($settings);
        $settings = $this->normalize_legacy_styling_keys($settings);

        return array_replace_recursive(Bato_Cookie_Consent_Settings_Defaults::defaults(), $settings);
    }

    private function normalize_legacy_content_keys(array $settings): array
    {
        if (! isset($settings['content']) || ! is_array($settings['content'])) {
            return $settings;
        }

        $settings['content'] = $this->normalize_content_node_keys($settings['content']);

        return $settings;
    }

    private function normalize_content_node_keys(array $node): array
    {
        $normalized = array();

        foreach ($node as $key => $value) {
            $key = $this->sanitize_content_key($key);
            $normalized[$key] = is_array($value) ? $this->normalize_content_node_keys($value) : $value;
        }

        return $normalized;
    }

    private function normalize_legacy_styling_keys(array $settings): array
    {
        if (isset($settings['styling']) && is_array($settings['styling'])) {
            $settings['styling'] = array(
                'className' => $settings['styling']['className'] ?? '',
            );
        }

        return $settings;
    }

    private function sanitize_choice($value, array $allowed, string $fallback): string
    {
        $value = sanitize_key((string) $value);

        return in_array($value, $allowed, true) ? $value : $fallback;
    }

    private function sanitize_content(array $content): array
    {
        $sanitized = array();

        foreach ($content as $language => $messages) {
            $language = $this->sanitize_choice($language, Bato_Cookie_Consent_Settings_Languages::supported(), '');

            if (! $language || ! is_array($messages)) {
                continue;
            }

            $sanitized[$language] = array_replace_recursive(
                Bato_Cookie_Consent_Settings_Defaults::content()[$language] ?? array(),
                $this->sanitize_content_node($messages)
            );
        }

        return $sanitized ?: Bato_Cookie_Consent_Settings_Defaults::content();
    }

    private function sanitize_content_node(array $node): array
    {
        $sanitized = array();

        foreach ($node as $key => $value) {
            $key = $this->sanitize_content_key($key);

            if (is_array($value)) {
                $sanitized[$key] = $this->sanitize_content_node($value);
            } else {
                $sanitized[$key] = $this->sanitize_formatted_text($value);
            }
        }

        return $sanitized;
    }

    private function sanitize_content_key($key): string
    {
        $key = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $key);
        $aliases = array(
            'acceptall' => 'acceptAll',
            'rejectall' => 'rejectAll',
            'hidedetails' => 'hideDetails',
            'buttontext' => 'buttonText',
        );
        $lookup = strtolower($key);

        return $aliases[$lookup] ?? $key;
    }

    private function sanitize_policy_links(array $links): array
    {
        $sanitized = array();

        foreach ($links as $link) {
            if (! is_array($link)) {
                continue;
            }

            $label = sanitize_text_field((string) ($link['label'] ?? ''));
            $href = esc_url_raw((string) ($link['href'] ?? ''));

            if (! $label || ! $href) {
                continue;
            }

            $target = (string) ($link['target'] ?? '');
            $rel = sanitize_text_field((string) ($link['rel'] ?? ''));

            $sanitized[] = array_filter(
                array(
                    'label' => $label,
                    'href' => $href,
                    'target' => in_array($target, array('_blank', '_self'), true) ? $target : '',
                    'rel' => $rel,
                )
            );
        }

        return $sanitized;
    }

    private function sanitize_formatted_text($value): string
    {
        $allowed = array(
            'a' => array(
                'href' => true,
                'target' => true,
                'rel' => true,
                'title' => true,
            ),
            'abbr' => array('title' => true),
            'b' => array(),
            'br' => array(),
            'code' => array(),
            'em' => array(),
            'i' => array(),
            'mark' => array(),
            'small' => array(),
            'span' => array(
                'class' => true,
                'title' => true,
            ),
            'strong' => array(),
            'sub' => array(),
            'sup' => array(),
            'u' => array(),
        );

        return wp_kses((string) $value, $allowed);
    }

}
