<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Intl\Data\Bundle\Reader;

use Symfony\Component\Intl\Data\Util\RecursiveArrayAccess;
use Symfony\Component\Intl\Exception\MissingResourceException;
use Symfony\Component\Intl\Exception\OutOfBoundsException;
use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
use Symfony\Component\Intl\Locale;

/**
 * Default implementation of {@link BundleEntryReaderInterface}.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 *
 * @see BundleEntryReaderInterface
 *
 * @internal
 */
class BundleEntryReader implements BundleEntryReaderInterface
{
    private BundleReaderInterface $reader;

    /**
     * A mapping of locale aliases to locales.
     */
    private array $localeAliases = [];

    /**
     * Creates an entry reader based on the given resource bundle reader.
     */
    public function __construct(BundleReaderInterface $reader)
    {
        $this->reader = $reader;
    }

    /**
     * Stores a mapping of locale aliases to locales.
     *
     * This mapping is used when reading entries and merging them with their
     * fallback locales. If an entry is read for a locale alias (e.g. "mo")
     * that points to a locale with a fallback locale ("ro_MD"), the reader
     * can continue at the correct fallback locale ("ro").
     *
     * @param array $localeAliases A mapping of locale aliases to locales
     */
    public function setLocaleAliases(array $localeAliases): void
    {
        $this->localeAliases = $localeAliases;
    }

    public function read(string $path, string $locale): mixed
    {
        return $this->reader->read($path, $locale);
    }

    public function readEntry(string $path, string $locale, array $indices, bool $fallback = true): mixed
    {
        $entry = null;
        $isMultiValued = false;
        $readSucceeded = false;
        $exception = null;
        $currentLocale = $locale;
        $testedLocales = [];

        while (null !== $currentLocale) {
            if (isset($this->localeAliases[$currentLocale])) {
                $currentLocale = $this->localeAliases[$currentLocale];
            }

            try {
                $data = $this->reader->read($path, $currentLocale);
                $currentEntry = RecursiveArrayAccess::get($data, $indices);
                $readSucceeded = true;

                $isCurrentTraversable = $currentEntry instanceof \Traversable;
                $isCurrentMultiValued = $isCurrentTraversable || \is_array($currentEntry);

                if (!$fallback || (!$isCurrentMultiValued && null !== $currentEntry)) {
                    return $currentEntry;
                }


                if ($isCurrentTraversable) {
                    $currentEntry = iterator_to_array($currentEntry);
                }

                if ($isCurrentMultiValued && $isMultiValued) {
                    $currentEntry = array_merge($currentEntry, $entry);
                }

                if (null !== $currentEntry) {
                    $entry = $currentEntry;
                }

                $isMultiValued = $isMultiValued || $isCurrentMultiValued;
            } catch (ResourceBundleNotFoundException $e) {
                $exception = $e;
            } catch (OutOfBoundsException $e) {
                $exception = $e;
            }

            $testedLocales[] = $currentLocale;

            if (!$fallback) {
                break;
            }

            $currentLocale = Locale::getFallback($currentLocale);
        }

        if ($isMultiValued) {
            return $entry;
        }

        if ($readSucceeded) {
            return $entry;
        }

        $errorMessage = \sprintf(
            'Couldn\'t read the indices [%s] for the locale "%s" in "%s".',
            implode('][', $indices),
            $locale,
            $path
        );

        if (\count($testedLocales) > 1) {
            array_shift($testedLocales);

            $errorMessage .= \sprintf(
                ' The indices also couldn\'t be found for the fallback locale(s) "%s".',
                implode('", "', $testedLocales)
            );
        }

        throw new MissingResourceException($errorMessage, 0, $exception);
    }
}
