<?php

declare(strict_types=1);

namespace SeoScope\i18n;

use SeoScope\Http\Request;

/**
 * Single source of truth for UI locales: codes allowed at runtime, setting key, and default.
 * String files live under /lang/{code}/messages.php; English is always the fallback inside Translator.
 */
final class SupportedLocales
{
    /** Cookie for UI language before login (mirrors app.base_path like session cookie). */
    public const UI_LOCALE_COOKIE = 'seoscope_ui_locale';

    /** Persisted in settings (per user) via SettingsRepository. */
    public const SETTING_KEY = 'app.locale';

    /** Default when missing, invalid, or not yet stored. */
    public const DEFAULT = 'en';

    /**
     * Whitelist: locale code => label for the language selector (native or English name per product choice).
     *
     * @var array<string, string>
     */
    private const BASE = [
        'en' => 'English',
    ];

    /**
     * @return list<string> Sorted locale codes (for validation and stable UI order).
     */
    public static function codes(): array
    {
        $k = array_keys(self::all());
        sort($k, SORT_STRING);

        return $k;
    }

    /**
     * @return array<string, string> code => label (same as whitelist)
     */
    public static function all(): array
    {
        $custom = self::customAll();
        if ($custom === []) {
            return self::BASE;
        }
        // Custom languages append to base (custom cannot override base codes).
        return self::BASE + $custom;
    }

    /**
     * @return array<string, string> code => label for user-added languages
     */
    public static function customAll(): array
    {
        $path = self::customLocalesPath();
        if ($path === null || !is_readable($path)) {
            return [];
        }
        $json = @file_get_contents($path);
        if (!is_string($json) || $json === '') {
            return [];
        }
        $data = json_decode($json, true);
        if (!is_array($data)) {
            return [];
        }
        $out = [];
        foreach ($data as $code => $label) {
            if (!is_string($code) || !is_string($label)) {
                continue;
            }
            $c = self::canon($code);
            if ($c === null) {
                continue;
            }
            // Prevent overriding base locales.
            if (array_key_exists($c, self::BASE)) {
                continue;
            }
            $lbl = trim($label);
            if ($lbl === '') {
                continue;
            }
            $out[$c] = $lbl;
        }
        if ($out === []) {
            return [];
        }
        ksort($out, SORT_STRING);
        return $out;
    }

    public static function isSupported(string $code): bool
    {
        $c = self::canon($code);
        if ($c === null) {
            return false;
        }

        return array_key_exists($c, self::all());
    }

    /**
     * Returns a supported code or {@see DEFAULT} if empty/invalid.
     */
    public static function normalize(?string $code): string
    {
        if ($code === null) {
            return self::DEFAULT;
        }
        $c = self::canon($code);

        return ($c !== null && array_key_exists($c, self::all())) ? $c : self::DEFAULT;
    }

    public static function label(string $code): string
    {
        $c = self::normalize($code);

        $all = self::all();
        $base = self::BASE;
        return $all[$c] ?? $base[self::DEFAULT];
    }

    /**
     * Remembers the UI locale on the client so /login, /forgot-password, etc. use the same
     * language as {@see SETTING_KEY} when the user is not authenticated.
     *
     * @param array<string, mixed> $config local.php
     */
    public static function setUiLocaleCookie(string $locale, array $config): void
    {
        $code = self::normalize($locale);
        $path = self::cookiePath($config);
        $expires = time() + (365 * 24 * 60 * 60);
        setcookie(self::UI_LOCALE_COOKIE, $code, [
            'expires' => $expires,
            'path' => $path,
            'secure' => Request::isHttps(),
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
        $_COOKIE[self::UI_LOCALE_COOKIE] = $code;
    }

    public static function localeFromCookie(): ?string
    {
        $raw = $_COOKIE[self::UI_LOCALE_COOKIE] ?? null;
        if (!is_string($raw) || $raw === '') {
            return null;
        }
        if (!self::isSupported($raw)) {
            return null;
        }

        return self::normalize($raw);
    }

    /**
     * @param array<string, mixed> $config
     */
    public static function cookiePath(array $config): string
    {
        $app = $config['app'] ?? [];
        $raw = trim((string) ($app['base_path'] ?? ''), '/');
        if ($raw !== '') {
            return '/' . $raw . '/';
        }

        return '/';
    }

    private static function canon(string $code): ?string
    {
        $t = trim($code);
        if ($t === '') {
            return null;
        }
        if (!preg_match('/^[a-z]{2}$/', $t = strtolower($t))) {
            return null;
        }

        return $t;
    }

    private static function customLocalesPath(): ?string
    {
        if (!defined('SEOSCOPE_ROOT')) {
            return null;
        }
        return SEOSCOPE_ROOT . '/storage/lang/locales.json';
    }
}
