<?php

declare(strict_types=1);

/*
 * This file is part of Contao.
 *
 * (c) Leo Feyer
 *
 * @license LGPL-3.0-or-later
 */

namespace Contao\CoreBundle\Intl;

use Contao\ArrayUtil;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class Locales
{
    /**
     * @var array<string>
     */
    private readonly array $locales;

    /**
     * @var array<string>
     */
    private readonly array $enabledLocales;

    public function __construct(
        private readonly TranslatorInterface&TranslatorBagInterface $translator,
        array $defaultLocales,
        array $defaultEnabledLocales,
        array $configLocales,
        array $configEnabledLocales,
        string $defaultLocale,
    ) {
        $this->locales = $this->filterLocales($defaultLocales, $configLocales);
        $this->enabledLocales = $this->filterLocales($defaultEnabledLocales, $configEnabledLocales, $defaultLocale);
    }

    /**
     * @return array<string, string> Translated locales indexed by their ICU locale IDs
     */
    public function getLocales(string|null $displayLocale = null, bool $addNativeSuffix = false): array
    {
        return $this->getDisplayNames($this->locales, $displayLocale, $addNativeSuffix);
    }

    /**
     * @return array<string, string> Translated enabled locales indexed by their ICU locale IDs
     */
    public function getEnabledLocales(string|null $displayLocale = null, bool $addNativeSuffix = false): array
    {
        return $this->getDisplayNames($this->enabledLocales, $displayLocale, $addNativeSuffix);
    }

    /**
     * @return array<string, string> Translated languages (without regions) indexed by their ICU locale IDs
     */
    public function getLanguages(string|null $displayLocale = null, bool $addNativeSuffix = false): array
    {
        $displayLocale ??= $this->translator->getLocale();

        return $this->getDisplayNames($this->getLanguageLocaleIds(), $displayLocale, $addNativeSuffix);
    }

    /**
     * @return array<string> ICU locale IDs
     */
    public function getLocaleIds(): array
    {
        return $this->locales;
    }

    /**
     * @return array<string> ICU locale IDs
     */
    public function getEnabledLocaleIds(): array
    {
        return $this->enabledLocales;
    }

    /**
     * @return array<string> Languages (without regions) as ICU locale IDs
     */
    public function getLanguageLocaleIds(): array
    {
        $localeIds = array_map(
            static fn ($localeId) => \Locale::composeLocale(
                array_intersect_key(
                    \Locale::parseLocale($localeId),
                    [\Locale::LANG_TAG => null, \Locale::SCRIPT_TAG => null],
                ),
            ),
            $this->locales,
        );

        $localeIds = array_values(array_unique(array_filter($localeIds)));

        sort($localeIds);

        return $localeIds;
    }

    /**
     * @return array<string, string> Translated locales indexed by their ICU locale IDs
     */
    public function getDisplayNames(array $localeIds, string|null $displayLocale = null, bool $addNativeSuffix = false): array
    {
        $displayLocale ??= $this->translator->getLocale();

        $locales = [];

        foreach ($localeIds as $localeId) {
            $langKey = 'LNG.'.$localeId;

            if ($this->translator->getCatalogue($displayLocale)->has($langKey, 'contao_languages')) {
                $label = $this->translator->trans($langKey, [], 'contao_languages', $displayLocale);
            } else {
                $label = \Locale::getDisplayName($localeId, $displayLocale);
            }

            if ($addNativeSuffix) {
                $nativeLabel = \Locale::getDisplayName($localeId, $localeId);

                if ($nativeLabel !== $label) {
                    $label .= ' - '.$nativeLabel;
                }
            }

            $locales[$localeId] = $label;
        }

        (new \Collator($displayLocale))->asort($locales);

        return $locales;
    }

    /**
     * Add, remove or replace locales as configured in the container configuration.
     */
    private function filterLocales(array $locales, array $filter, string|null $default = null): array
    {
        $locales = ArrayUtil::alterListByConfig($locales, $filter);

        // The default locale must be the first supported language (see contao/core#6533)
        if (null !== $default) {
            if (\in_array($default, $locales, true)) {
                unset($locales[array_search($default, $locales, true)]);
            }
            array_unshift($locales, $default);
        }

        return $locales;
    }
}
