<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\I18n;

defined('ABSPATH') || exit;

class JsTranslationManager
{
    public const OPTION = 'apd_js_translation_locales';
    private const SOURCE_FILE = ASUKA_PRODUCT_DESIGNER_PATH . 'resources/i18n/asukadesigner.en.json';
    private const RELEASE_SOURCE_FILE = ASUKA_PRODUCT_DESIGNER_PATH . 'assets/vendor/asuka-designer/translation-catalog.json';
    private const RENAME_FILE = ASUKA_PRODUCT_DESIGNER_PATH . 'resources/i18n/asukadesigner.key-renames.json';

    public function getBaseCatalog() : array
    {
        static $catalog = null;
        if ($catalog !== null) {
            return $catalog;
        }

        $sourceFile = file_exists(self::RELEASE_SOURCE_FILE) ? self::RELEASE_SOURCE_FILE : self::SOURCE_FILE;
        $json = file_exists($sourceFile) ? file_get_contents($sourceFile) : false;
        $decoded = is_string($json) ? json_decode($json, true) : null;
        $catalog = is_array($decoded['translations'] ?? null)
            ? $decoded['translations']
            : (is_array($decoded) ? $decoded : []);

        return $catalog;
    }

    public function getBaseFlatCatalog() : array
    {
        static $flat = null;
        if ($flat !== null) {
            return $flat;
        }

        $flat = array_replace(
            $this->flattenCatalog($this->getBaseCatalog()),
            $this->getMenuFlatCatalog()
        );

        return $flat;
    }

    public function getStoredLocales() : array
    {
        $option = get_option(self::OPTION, []);
        $locales = [];

        if (! is_array($option)) {
            return $locales;
        }

        foreach (($option['locales'] ?? []) as $locale => $payload) {
            $code = self::normalizeLocale((string) $locale);
            if ($code === '' || $code === 'en') {
                continue;
            }

            $translations = is_array($payload['translations'] ?? null) ? $payload['translations'] : [];
            $locales[$code] = [
                'label' => sanitize_text_field((string) ($payload['label'] ?? '')),
                'translations' => $this->sanitizeTranslations($translations),
                'updated_at' => sanitize_text_field((string) ($payload['updated_at'] ?? '')),
            ];
        }

        ksort($locales, SORT_NATURAL | SORT_FLAG_CASE);

        return $locales;
    }

    public function getLocaleTranslations(string $locale) : array
    {
        $locales = $this->getStoredLocales();
        $normalized = self::normalizeLocale($locale);
        return is_array($locales[$normalized]['translations'] ?? null) ? $locales[$normalized]['translations'] : [];
    }

    public function saveLocale(string $locale, array $translations, string $label = '') : void
    {
        $normalized = self::normalizeLocale($locale);
        if ($normalized === '' || $normalized === 'en') {
            return;
        }

        $option = get_option(self::OPTION, []);
        if (! is_array($option)) {
            $option = [];
        }
        if (! isset($option['locales']) || ! is_array($option['locales'])) {
            $option['locales'] = [];
        }

        $existing = is_array($option['locales'][$normalized] ?? null) ? $option['locales'][$normalized] : [];
        $label = $label !== '' ? sanitize_text_field($label) : (string) ($existing['label'] ?? '');
        if ($label === '') {
            $label = $this->getLocaleDisplayName($normalized);
        }

        $option['locales'][$normalized] = [
            'label' => $label,
            'translations' => $this->sanitizeTranslations($translations),
            'updated_at' => current_time('mysql'),
        ];

        update_option(self::OPTION, $option, false);
    }

    public function ensureLocale(string $locale, string $label = '') : void
    {
        $normalized = self::normalizeLocale($locale);
        if ($normalized === '' || $normalized === 'en') {
            return;
        }

        $locales = $this->getStoredLocales();
        if (isset($locales[$normalized])) {
            return;
        }

        $this->saveLocale($normalized, [], $label);
    }

    public function deleteLocale(string $locale) : void
    {
        $normalized = self::normalizeLocale($locale);
        if ($normalized === '' || $normalized === 'en') {
            return;
        }

        $option = get_option(self::OPTION, []);
        if (! is_array($option) || ! isset($option['locales']) || ! is_array($option['locales'])) {
            return;
        }

        unset($option['locales'][$normalized]);
        update_option(self::OPTION, $option, false);
    }

    public function buildRuntimeConfig(?string $locale = null, bool $warnOnMissing = false) : array
    {
        $requestedLocale = self::normalizeLocale($locale ?: determine_locale() ?: get_locale());
        if ($requestedLocale === '') {
            $requestedLocale = 'en';
        }

        $languageLocale = $this->extractLanguageLocale($requestedLocale);
        $activeLocale = $requestedLocale;

        $baseFlat = $this->getBaseFlatCatalog();
        $translations = [
            'en' => $this->expandFlatCatalog($baseFlat),
        ];

        if ($requestedLocale !== 'en') {
            $languageFlat = array_replace($baseFlat, $this->getCurrentGettextFlatCatalog());
            if ($languageLocale !== '' && $languageLocale !== 'en') {
                $languageFlat = array_replace($languageFlat, $this->getLocaleTranslations($languageLocale));
                $translations[$languageLocale] = $this->expandFlatCatalog($languageFlat);
            }

            $requestedFlat = $languageFlat;
            if ($requestedLocale !== $languageLocale) {
                $requestedFlat = array_replace($requestedFlat, $this->getLocaleTranslations($requestedLocale));
            }
            $translations[$requestedLocale] = $this->expandFlatCatalog($requestedFlat);
        }

        return [
            'locale' => $activeLocale,
            'fallbackLocale' => 'en',
            'warnOnMissing' => $warnOnMissing,
            'translations' => $translations,
        ];
    }

    public function getEditorRows(string $locale) : array
    {
        $base = $this->getBaseFlatCatalog();
        $overrides = $this->getLocaleTranslations($locale);
        $rows = [];

        foreach ($base as $key => $defaultValue) {
            $namespace = explode('.', (string) $key, 2)[0] ?: 'misc';
            $rows[] = [
                'key' => $key,
                'namespace' => $namespace,
                'default' => (string) $defaultValue,
                'translation' => (string) ($overrides[$key] ?? ''),
                'translated' => isset($overrides[$key]) && $overrides[$key] !== '',
            ];
        }

        usort($rows, static function (array $left, array $right) : int {
            return [$left['namespace'], $left['key']] <=> [$right['namespace'], $right['key']];
        });

        return $rows;
    }

    public function getLocaleStats(string $locale) : array
    {
        $base = $this->getBaseFlatCatalog();
        $normalized = self::normalizeLocale($locale);
        if ($normalized === 'en') {
            return [
                'total' => count($base),
                'translated' => count($base),
                'missing' => 0,
            ];
        }

        $language = $this->extractLanguageLocale($normalized);
        $translations = array_replace(
            $language !== '' && $language !== $normalized ? $this->getLocaleTranslations($language) : [],
            $this->getLocaleTranslations($normalized)
        );
        $translated = 0;
        foreach (array_keys($base) as $key) {
            if (isset($translations[$key]) && $translations[$key] !== '') {
                $translated += 1;
            }
        }

        return [
            'total' => count($base),
            'translated' => $translated,
            'missing' => max(0, count($base) - $translated),
        ];
    }

    public function getAvailableLocales() : array
    {
        $locales = [
            'en' => [
                'label' => __('English (default catalog)', 'asuka-product-designer'),
                'translations' => [],
                'updated_at' => '',
            ],
        ];

        foreach ($this->getStoredLocales() as $code => $payload) {
            $locales[$code] = $payload + ['label' => $this->getLocaleDisplayName($code)];
            if (($locales[$code]['label'] ?? '') === '') {
                $locales[$code]['label'] = $this->getLocaleDisplayName($code);
            }
        }

        return $locales;
    }

    public function getLocaleDisplayName(string $locale) : string
    {
        $normalized = self::normalizeLocale($locale);
        if ($normalized === 'en') {
            return 'en';
        }

        if (function_exists('locale_get_display_name')) {
            $display = locale_get_display_name(str_replace('_', '-', $normalized), get_user_locale());
            if (is_string($display) && $display !== '') {
                return $display;
            }
        }

        return $normalized;
    }

    public static function normalizeLocale(string $locale) : string
    {
        $locale = trim(str_replace('-', '_', $locale));
        $locale = preg_replace('/[^A-Za-z0-9_]/', '', $locale) ?: '';
        if ($locale === '') {
            return '';
        }

        $parts = array_values(array_filter(explode('_', $locale), 'strlen'));
        if ($parts === []) {
            return '';
        }

        $language = strtolower((string) array_shift($parts));
        if ($parts === []) {
            return $language;
        }

        $normalizedParts = [];
        foreach ($parts as $index => $part) {
            $normalizedParts[] = $index === 0 ? strtoupper($part) : $part;
        }

        return $language . '_' . implode('_', $normalizedParts);
    }

    public function flattenCatalog(array $catalog, string $prefix = '') : array
    {
        $flat = [];
        foreach ($catalog as $key => $value) {
            $compound = $prefix !== '' ? $prefix . '.' . $key : (string) $key;
            if (is_array($value)) {
                $flat = array_replace($flat, $this->flattenCatalog($value, $compound));
                continue;
            }
            $flat[$compound] = (string) $value;
        }

        return $flat;
    }

    public function expandFlatCatalog(array $flat) : array
    {
        $expanded = [];
        foreach ($flat as $compound => $value) {
            $segments = array_values(array_filter(explode('.', (string) $compound), 'strlen'));
            if ($segments === []) {
                continue;
            }

            $pointer = &$expanded;
            $lastIndex = count($segments) - 1;
            foreach ($segments as $index => $segment) {
                if ($index === $lastIndex) {
                    $pointer[$segment] = (string) $value;
                    break;
                }

                if (! isset($pointer[$segment]) || ! is_array($pointer[$segment])) {
                    $pointer[$segment] = [];
                }
                $pointer = &$pointer[$segment];
            }
            unset($pointer);
        }

        return $expanded;
    }

    private function sanitizeTranslations(array $translations) : array
    {
        $allowedKeys = array_keys($this->getBaseFlatCatalog());
        $allowedLookup = array_fill_keys($allowedKeys, true);
        $renameMap = $this->getLegacyKeyRenames();
        $sanitized = [];

        foreach ($translations as $key => $value) {
            $compound = (string) $key;
            if (isset($renameMap[$compound])) {
                $compound = (string) $renameMap[$compound];
            }
            if (! isset($allowedLookup[$compound])) {
                continue;
            }

            $text = sanitize_textarea_field((string) $value);
            if ($text === '') {
                continue;
            }
            $sanitized[$compound] = $text;
        }

        ksort($sanitized, SORT_NATURAL | SORT_FLAG_CASE);

        return $sanitized;
    }

    private function getLegacyKeyRenames() : array
    {
        static $map = null;
        if ($map !== null) {
            return $map;
        }

        $json = file_exists(self::RENAME_FILE) ? file_get_contents(self::RENAME_FILE) : false;
        $decoded = is_string($json) ? json_decode($json, true) : null;
        $map = [];

        if (is_array($decoded)) {
            foreach ($decoded as $oldKey => $newKey) {
                $old = trim((string) $oldKey);
                $new = trim((string) $newKey);
                if ($old === '' || $new === '') {
                    continue;
                }
                $map[$old] = $new;
            }
        }

        return $map;
    }

    private function extractLanguageLocale(string $locale) : string
    {
        $normalized = self::normalizeLocale($locale);
        if ($normalized === '') {
            return '';
        }
        $parts = explode('_', $normalized);
        return strtolower((string) ($parts[0] ?? ''));
    }

    private function getCurrentGettextFlatCatalog() : array
    {
        static $flat = null;
        if ($flat !== null) {
            return $flat;
        }

        $flat = array_replace(
            JsTranslationGettext::flatCatalog(),
            $this->getGettextMenuFlatCatalog()
        );

        return $flat;
    }

    private function getMenuFlatCatalog() : array
    {
        static $flat = null;
        if ($flat !== null) {
            return $flat;
        }

        $flat = $this->getKnownMenuKindFlatCatalog();

        foreach ($this->getDiscoveredMenuItems() as $item) {
            $id = (string) ($item['id'] ?? '');
            $kind = (string) ($item['kind'] ?? '');
            $label = (string) ($item['label'] ?? '');

            if ($id !== '' && $label !== '') {
                $flat['menu.' . $id] = $label;
            }

            if ($kind !== '' && $label !== '' && ! isset($flat['menu.' . $kind])) {
                $flat['menu.' . $kind] = $label;
            }
        }

        ksort($flat, SORT_NATURAL | SORT_FLAG_CASE);

        return $flat;
    }

    private function getGettextMenuFlatCatalog() : array
    {
        $catalog = [];
        $translations = get_translations_for_domain('asuka-product-designer');
        foreach ($this->getMenuFlatCatalog() as $key => $label) {
            $catalog[$key] = $translations->translate($label, $key);
        }

        return $catalog;
    }

    private function getKnownMenuKindFlatCatalog() : array
    {
        return [
            'menu.views' => __('Views', 'asuka-product-designer'),
            'menu.designs' => __('Designs', 'asuka-product-designer'),
            'menu.text' => __('Text', 'asuka-product-designer'),
            'menu.image' => __('Image', 'asuka-product-designer'),
            'menu.images' => __('Images', 'asuka-product-designer'),
            'menu.shapes' => __('Shapes', 'asuka-product-designer'),
            'menu.layers' => __('Layers', 'asuka-product-designer'),
            'menu.zoom' => __('Zoom', 'asuka-product-designer'),
            'menu.download' => __('Download', 'asuka-product-designer'),
            'menu.magnifier' => __('Magnifier', 'asuka-product-designer'),
            'menu.ruler' => __('Ruler', 'asuka-product-designer'),
            'menu.history' => __('History', 'asuka-product-designer'),
            'menu.state' => __('State', 'asuka-product-designer'),
            'menu.tools' => __('Tools', 'asuka-product-designer'),
            'menu.pricing' => __('Pricing', 'asuka-product-designer'),
            'menu.pricingrules' => __('Pricing rules', 'asuka-product-designer'),
            'menu.pricingRules' => __('Pricing rules', 'asuka-product-designer'),
            'menu.elements' => __('Elements', 'asuka-product-designer'),
            'menu.drawingareas' => __('Drawing areas', 'asuka-product-designer'),
            'menu.drawingAreas' => __('Drawing areas', 'asuka-product-designer'),
            'menu.fixedslots' => __('Fixed slots', 'asuka-product-designer'),
            'menu.fixedSlots' => __('Fixed slots', 'asuka-product-designer'),
            'menu.marker' => __('Marker', 'asuka-product-designer'),
            'menu.colors' => __('Colors', 'asuka-product-designer'),
            'menu.studio' => __('Studio', 'asuka-product-designer'),
            'menu.font-family' => __('Font family', 'asuka-product-designer'),
            'menu.text-formatting' => __('Text formatting', 'asuka-product-designer'),
            'menu.text-curve' => __('Text curve', 'asuka-product-designer'),
            'menu.transform' => __('Transform', 'asuka-product-designer'),
            'menu.upload-image' => __('Upload image', 'asuka-product-designer'),
            'menu.pixabay-image' => __('Pixabay image', 'asuka-product-designer'),
            'menu.pexels-image' => __('Pexels image', 'asuka-product-designer'),
            'menu.facebook-image' => __('Facebook image', 'asuka-product-designer'),
            'menu.qrcode-image' => __('QR code image', 'asuka-product-designer'),
        ];
    }

    private function getDiscoveredMenuItems() : array
    {
        static $items = null;
        if ($items !== null) {
            return $items;
        }

        $items = [];
        $postIds = get_posts([
            'post_type' => ['apd_template', 'apd_product_config'],
            'post_status' => 'any',
            'numberposts' => -1,
            'fields' => 'ids',
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'suppress_filters' => false,
        ]);

        foreach ($postIds as $postId) {
            $layout = get_post_meta((int) $postId, '_apd_layout', true);
            if (! is_string($layout) || trim($layout) === '') {
                continue;
            }

            $decoded = json_decode($layout, true);
            if (! is_array($decoded)) {
                continue;
            }

            foreach ($this->extractMenuItemsFromLayout($decoded) as $entry) {
                $id = (string) ($entry['id'] ?? '');
                if ($id === '') {
                    $id = (string) ($entry['kind'] ?? '');
                }

                $label = trim((string) ($entry['label'] ?? ''));
                if ($label === '') {
                    $label = (string) ($entry['kind'] ?? $id);
                }

                $items[] = [
                    'id' => sanitize_key($id),
                    'kind' => sanitize_key((string) ($entry['kind'] ?? '')),
                    'label' => sanitize_text_field($label),
                ];
            }
        }

        return array_values(array_filter($items, static function (array $item) : bool {
            return ($item['id'] ?? '') !== '' || ($item['kind'] ?? '') !== '';
        }));
    }

    private function extractMenuItemsFromLayout(array $layout) : array
    {
        $items = [];
        foreach (['top', 'left', 'right', 'bottom'] as $zone) {
            $zoneConfig = $layout[$zone] ?? null;
            if (! is_array($zoneConfig)) {
                continue;
            }

            $zoneItems = [];
            if (isset($zoneConfig['items']) && is_array($zoneConfig['items'])) {
                $zoneItems = $zoneConfig['items'];
            } elseif (array_is_list($zoneConfig)) {
                $zoneItems = $zoneConfig;
            }

            foreach ($zoneItems as $item) {
                if (! is_array($item)) {
                    continue;
                }

                $items[] = [
                    'id' => (string) ($item['id'] ?? ''),
                    'kind' => (string) ($item['kind'] ?? ''),
                    'label' => (string) ($item['label'] ?? ''),
                ];
            }
        }

        return $items;
    }
}
