<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

final class FileTypeSettings
{
    public const OPTION_NAME = 'shieldx_file_type_settings';
    public const CONTEXT_MALWARE = 'malware';
    public const CONTEXT_BACKUP = 'backup';
    private const DEFAULT_TYPES = [
        'php', 'php0', 'php1', 'php2', 'php3', 'php4', 'php5', 'php6', 'php7', 'php8', 'php9',
        'phtml', 'phar', 'inc', 'js',
        'htaccess', 'env', 'sql', 'json',
        'other',
    ];

    /** @var array<string,mixed> */
    private $settings;

    /**
     * @param array<string,mixed> $settings
     */
    public function __construct(array $settings = [])
    {
        $this->settings = self::normalize($settings);
    }

    public static function fromWordPress(): self
    {
        $cached = RequestCache::get(self::class);
        if ($cached instanceof self) {
            return $cached;
        }

        $saved = function_exists('get_option') ? get_option(self::OPTION_NAME, []) : [];

        $instance = new self(is_array($saved) ? $saved : []);
        RequestCache::rememberOption(self::class, self::OPTION_NAME, $instance);

        return $instance;
    }

    /**
     * @return array<string,mixed>
     */
    public function all(): array
    {
        return $this->settings;
    }

    /**
     * @param array<string,mixed> $settings
     */
    public function save(array $settings): void
    {
        $this->settings = self::normalize($settings);

        if (function_exists('update_option')) {
            update_option(self::OPTION_NAME, $this->settings, false);
        }

        RequestCache::rememberOption(self::class, self::OPTION_NAME, $this);
    }

    /**
     * @return array<int,string>
     */
    public function selected(string $context): array
    {
        $context = self::sanitizeContext($context);

        return (array) ($this->settings[$context] ?? self::defaultTypes());
    }

    public function allowsPath(string $path, string $context): bool
    {
        $token = self::tokenForPath($path);
        $selected = array_fill_keys($this->selected($context), true);

        return isset($selected[$token]);
    }

    /**
     * @return array<string,array<string,mixed>>
     */
    public static function catalog(bool $translate = true): array
    {
        return [
            'code' => [
                'label' => $translate ? __('Code & executable', 'xerotact-shield-x-security') : 'Code & executable',
                'description' => $translate ? __('PHP, plugin/theme code, web assets, and script-bearing files.', 'xerotact-shield-x-security') : 'PHP, plugin/theme code, web assets, and script-bearing files.',
                'media' => false,
                'types' => [
                    'php' => 'PHP',
                    'php0' => 'PHP0',
                    'php1' => 'PHP1',
                    'php2' => 'PHP2',
                    'php3' => 'PHP3',
                    'php4' => 'PHP4',
                    'php5' => 'PHP5',
                    'php6' => 'PHP6',
                    'php7' => 'PHP7',
                    'php8' => 'PHP8',
                    'php9' => 'PHP9',
                    'phtml' => 'PHTML',
                    'phar' => 'PHAR',
                    'inc' => 'INC',
                    'js' => 'JavaScript',
                    'css' => 'CSS',
                    'html' => 'HTML',
                    'htm' => 'HTM',
                ],
            ],
            'config' => [
                'label' => $translate ? __('Config, text & data', 'xerotact-shield-x-security') : 'Config, text & data',
                'description' => $translate ? __('Configuration files, logs, dumps, manifests, and text formats.', 'xerotact-shield-x-security') : 'Configuration files, logs, dumps, manifests, and text formats.',
                'media' => false,
                'types' => [
                    'htaccess' => '.htaccess',
                    'env' => '.env',
                    'txt' => 'TXT',
                    'log' => 'LOG',
                    'sql' => 'SQL',
                    'xml' => 'XML',
                    'json' => 'JSON',
                    'md' => 'Markdown',
                ],
            ],
            'archives' => [
                'label' => $translate ? __('Backups & archives', 'xerotact-shield-x-security') : 'Backups & archives',
                'description' => $translate ? __('Backup suffixes and archive formats. Off by default to avoid scanning large or stale archive payloads.', 'xerotact-shield-x-security') : 'Backup suffixes and archive formats. Off by default to avoid scanning large or stale archive payloads.',
                'media' => false,
                'default_selected' => false,
                'types' => [
                    'bak' => 'BAK',
                    'old' => 'OLD',
                    'orig' => 'ORIG',
                    'save' => 'SAVE',
                    'swp' => 'SWP',
                    'zip' => 'ZIP',
                    'tar' => 'TAR',
                    'gz' => 'GZ',
                ],
            ],
            'media' => [
                'label' => $translate ? __('Media & generated assets', 'xerotact-shield-x-security') : 'Media & generated assets',
                'description' => $translate ? __('Large uploads and generated assets. Off by default for faster scans and backups.', 'xerotact-shield-x-security') : 'Large uploads and generated assets. Off by default for faster scans and backups.',
                'media' => true,
                'types' => [
                    'jpg' => 'JPG',
                    'jpeg' => 'JPEG',
                    'png' => 'PNG',
                    'gif' => 'GIF',
                    'webp' => 'WEBP',
                    'avif' => 'AVIF',
                    'svg' => 'SVG',
                    'ico' => 'ICO',
                    'pdf' => 'PDF',
                    'mp3' => 'MP3',
                    'wav' => 'WAV',
                    'ogg' => 'OGG',
                    'mp4' => 'MP4',
                    'mov' => 'MOV',
                    'avi' => 'AVI',
                    'webm' => 'WEBM',
                    'ttf' => 'TTF',
                    'otf' => 'OTF',
                    'woff' => 'WOFF',
                    'woff2' => 'WOFF2',
                ],
            ],
            'other' => [
                'label' => $translate ? __('Other / extensionless', 'xerotact-shield-x-security') : 'Other / extensionless',
                'description' => $translate ? __('Unlisted extensions and files without an extension. Kept on by default so unusual webshell names are not missed.', 'xerotact-shield-x-security') : 'Unlisted extensions and files without an extension. Kept on by default so unusual webshell names are not missed.',
                'media' => false,
                'types' => [
                    'other' => $translate ? __('Other', 'xerotact-shield-x-security') : 'Other',
                ],
            ],
        ];
    }

    /**
     * @return array<string,string>
     */
    public static function labels(): array
    {
        $labels = [];
        foreach (self::catalog(false) as $group) {
            foreach ((array) ($group['types'] ?? []) as $token => $label) {
                $labels[(string) $token] = (string) $label;
            }
        }

        return $labels;
    }

    /**
     * @return array<int,string>
     */
    public static function defaultTypes(): array
    {
        return self::DEFAULT_TYPES;
    }

    /**
     * @return array<int,string>
     */
    public static function mediaTypes(): array
    {
        $media = [];
        foreach (self::catalog(false) as $group) {
            if (empty($group['media'])) {
                continue;
            }

            foreach ((array) ($group['types'] ?? []) as $token => $label) {
                $media[] = (string) $token;
            }
        }

        return $media;
    }

    /**
     * @param array<string,mixed> $settings
     * @return array<string,mixed>
     */
    public static function normalize(array $settings): array
    {
        return [
            self::CONTEXT_MALWARE => self::sanitizeTypes((array) ($settings[self::CONTEXT_MALWARE] ?? self::defaultTypes())),
            self::CONTEXT_BACKUP => self::sanitizeTypes((array) ($settings[self::CONTEXT_BACKUP] ?? self::defaultTypes())),
        ];
    }

    /**
     * @param array<int,mixed> $types
     * @return array<int,string>
     */
    public static function sanitizeTypes(array $types): array
    {
        $allowed = array_fill_keys(array_keys(self::labels()), true);
        $selected = [];

        foreach ($types as $type) {
            if (!is_scalar($type)) {
                continue;
            }

            $type = strtolower(trim((string) $type));
            $type = ltrim($type, '.');
            $type = preg_replace('~[^a-z0-9_-]+~', '', $type) ?: '';
            if ($type !== '' && isset($allowed[$type])) {
                $selected[$type] = $type;
            }
        }

        return $selected ? array_values($selected) : self::defaultTypes();
    }

    public static function tokenForPath(string $path): string
    {
        $path = str_replace('\\', '/', strtolower($path));
        $basename = basename($path);
        if ($basename === '.htaccess') {
            return 'htaccess';
        }

        if ($basename === '.env' || strpos($basename, '.env.') === 0) {
            return 'env';
        }

        $extension = pathinfo($basename, PATHINFO_EXTENSION);
        $extension = is_string($extension) ? strtolower($extension) : '';
        $labels = self::labels();

        return $extension !== '' && isset($labels[$extension]) ? $extension : 'other';
    }

    private static function sanitizeContext(string $context): string
    {
        return $context === self::CONTEXT_BACKUP ? self::CONTEXT_BACKUP : self::CONTEXT_MALWARE;
    }
}
