<?php
declare(strict_types=1);

namespace App\Support;

final class Translator
{
    private string $locale;
    private string $fallbackLocale;
    private string $languageDirectory;
    /** @var array<string, array<string, mixed>> */
    private array $loaded = [];
    /** @var array<string, mixed> */
    private array $dictionary = [];
    /** @var array<string, string> */
    private array $overrides = [];
    /** @var array<string, string> */
    private array $fallbackOverrides = [];

    public function __construct(
        string $locale,
        string $fallbackLocale = 'ar',
        ?string $languageDirectory = null,
        array $overrides = [],
        array $fallbackOverrides = [],
    ) {
        $this->fallbackLocale = $this->normalizeLocale($fallbackLocale) ?? 'ar';
        $this->languageDirectory = $languageDirectory ?? app_root_path('resources', 'lang');
        $this->locale = $this->resolveLocale($locale);
        $this->dictionary = $this->loadLocale($this->locale);
        $this->overrides = $this->normalizeOverrides($overrides);
        $this->fallbackOverrides = $this->normalizeOverrides($fallbackOverrides);
    }

    public function get(string $key, array $replace = []): string
    {
        $value = array_key_exists($key, $this->overrides) ? $this->overrides[$key] : null;
        if ($value === null) {
            $value = $this->getFromDictionary($this->dictionary, $key);
        }

        if ($value === null && $this->locale !== $this->fallbackLocale) {
            $value = array_key_exists($key, $this->fallbackOverrides) ? $this->fallbackOverrides[$key] : null;
            if ($value === null) {
                $value = $this->getFromDictionary($this->loadLocale($this->fallbackLocale), $key);
            }
        }

        if ($value === null) {
            $value = $key;
        }

        if (!is_string($value) && !is_numeric($value)) {
            return $key;
        }

        return strtr((string) $value, $this->normalizeReplacements($replace));
    }

    public function locale(): string
    {
        return $this->locale;
    }

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

    private function resolveLocale(string $locale): string
    {
        return $this->normalizeLocale($locale) ?? $this->fallbackLocale;
    }

    private function normalizeLocale(string $locale): ?string
    {
        $locale = strtolower(str_replace('_', '-', trim($locale)));

        if ($locale === '' || !preg_match('/^[a-z]{2,3}(?:[-_][a-z0-9]{2,8})*$/i', $locale)) {
            return null;
        }

        return $locale;
    }

    private function localeFileExists(string $locale): bool
    {
        return is_file($this->languageDirectory . DIRECTORY_SEPARATOR . $locale . '.php');
    }

    /**
     * @return array<string, mixed>
     */
    private function loadLocale(string $locale): array
    {
        if (array_key_exists($locale, $this->loaded)) {
            return $this->loaded[$locale];
        }

        $file = $this->languageDirectory . DIRECTORY_SEPARATOR . $locale . '.php';

        if (!is_file($file)) {
            return $this->loaded[$locale] = [];
        }

        $dictionary = require $file;

        if (!is_array($dictionary)) {
            $dictionary = [];
        }

        return $this->loaded[$locale] = $dictionary;
    }

    /**
     * @param array<string, mixed> $dictionary
     */
    private function getFromDictionary(array $dictionary, string $key): mixed
    {
        $cursor = $dictionary;

        foreach (explode('.', $key) as $segment) {
            if (!is_array($cursor) || !array_key_exists($segment, $cursor)) {
                return null;
            }

            $cursor = $cursor[$segment];
        }

        return $cursor;
    }

    /**
     * @param array<string, mixed> $replace
     * @return array<string, string>
     */

    /** @param array<string, mixed> $overrides @return array<string, string> */
    private function normalizeOverrides(array $overrides): array
    {
        $normalized = [];
        foreach ($overrides as $key => $value) {
            if (!is_string($key) || (!is_string($value) && !is_numeric($value))) {
                continue;
            }
            $normalized[$key] = (string) $value;
        }
        return $normalized;
    }

    private function normalizeReplacements(array $replace): array
    {
        $normalized = [];

        foreach ($replace as $key => $value) {
            $normalized[':' . $key] = (string) $value;
        }

        return $normalized;
    }
}
