<?php
declare(strict_types=1);

namespace Autogedal\FrontendI18n\Console\Command;

use Autogedal\FrontendI18n\Model\AreaPathsResolver;
use Autogedal\FrontendI18n\Model\PhraseCollector;
use Autogedal\FrontendI18n\Model\TranslationCsvReader;
use Magento\Framework\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Output\OutputInterface;

class CollectFrontendPhrasesCommand extends Command
{
    private const COMMAND_NAME = 'autogedal:i18n:collect';
    private const OPTION_AREA = 'area';
    private const OPTION_CSV = 'csv';
    private const OPTION_IGNORE_CSV = 'ignore-csv';
    private const OPTION_APPEND = 'append';
    private const OPTION_INCLUDE_VENDOR = 'include-vendor';

    public function __construct(
        private readonly AreaPathsResolver $areaPathsResolver,
        private readonly PhraseCollector $phraseCollector,
        private readonly TranslationCsvReader $translationCsvReader,
        ?string $name = null
    ) {
        parent::__construct($name);
    }

    protected function configure(): void
    {
        $this->setName(self::COMMAND_NAME)
            ->setDescription('Collect app/code phrases and append missing rows to a translation CSV file')
            ->setDefinition([
                new InputOption(
                    self::OPTION_AREA,
                    null,
                    InputOption::VALUE_REQUIRED,
                    'Area to scan: frontend or backend, based on Adminhtml paths',
                    AreaPathsResolver::AREA_FRONTEND
                ),
                new InputOption(
                    self::OPTION_CSV,
                    'c',
                    InputOption::VALUE_REQUIRED,
                    'Final translation CSV path to update in place, for example app/design/frontend/.../i18n/ro_RO.csv'
                ),
                new InputOption(
                    self::OPTION_IGNORE_CSV,
                    'i',
                    InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
                    'CSV path to exclude from the append pass. Repeat for more files'
                ),
                new InputOption(
                    self::OPTION_APPEND,
                    null,
                    InputOption::VALUE_NONE,
                    'Append only new rows when the CSV path already exists'
                ),
                new InputOption(
                    self::OPTION_INCLUDE_VENDOR,
                    null,
                    InputOption::VALUE_NONE,
                    'Also scan matching vendor module paths under /vendor'
                ),
            ]);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            $area = strtolower(trim((string)$input->getOption(self::OPTION_AREA)));
            $this->areaPathsResolver->assertValidArea($area);

            $csvPath = trim((string)$input->getOption(self::OPTION_CSV));
            if ($csvPath === '') {
                throw new \InvalidArgumentException('The --csv option is required.');
            }

            $includeVendor = (bool)$input->getOption(self::OPTION_INCLUDE_VENDOR);
            $phrases = $this->phraseCollector->collect($area, $includeVendor);
            $ignoreCsvPaths = array_values(array_filter(array_map('trim', $input->getOption(self::OPTION_IGNORE_CSV) ?: [])));
            $ignoreSources = $ignoreCsvPaths ? array_keys($this->translationCsvReader->readMany($ignoreCsvPaths)) : [];
            $phrases = $this->filterIgnoredPhrases($phrases, $ignoreSources);
            [$csvPath, $appendMode] = $this->resolveCsvPath($input, $output, $csvPath);
            $existingSources = is_file($csvPath) ? array_keys($this->translationCsvReader->read($csvPath)) : [];
            $existingStats = $this->translationCsvReader->analyze($csvPath);
            $writtenCount = $this->appendMissingRows($csvPath, $phrases, $existingSources, $appendMode);

            $output->writeln('<info>Scanned ' . count($phrases) . ' unique ' . $area . ' phrases.</info>');
            if ($includeVendor) {
                $output->writeln('<info>Vendor scan enabled.</info>');
            }
            if ($ignoreSources) {
                $output->writeln('<info>Ignored ' . count($ignoreSources) . ' phrase(s) from ' . count($ignoreCsvPaths) . ' ignore CSV file(s).</info>');
            }
            $output->writeln('<info>Existing translated rows: ' . $existingStats['translated'] . '</info>');
            $output->writeln('<info>Existing untranslated rows: ' . $existingStats['untranslated'] . '</info>');
            $output->writeln('<info>Updated CSV: ' . $csvPath . '</info>');
            $output->writeln('<info>' . $writtenCount . ' missing row(s) appended as valid source/target pairs.</info>');

            return Cli::RETURN_SUCCESS;
        } catch (\InvalidArgumentException $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');
            return Cli::RETURN_FAILURE;
        } catch (\Throwable $e) {
            $output->writeln('<error>' . $e->getMessage() . '</error>');
            return Cli::RETURN_FAILURE;
        }
    }

    /**
     * @param string[] $phrases
     */
    private function appendMissingRows(string $path, array $phrases, array $existingSources = [], bool $append = false): int
    {
        $this->ensureParentDirectory($path);
        $handle = fopen($path, $append ? 'ab' : 'wb');
        if ($handle === false) {
            throw new \RuntimeException('Unable to open output file: ' . $path);
        }

        $existingSources = array_fill_keys($existingSources, true);
        $written = 0;

        foreach ($phrases as $phrase) {
            if (isset($existingSources[$phrase])) {
                continue;
            }

            fputcsv($handle, [$phrase, $phrase]);
            $written++;
        }

        fclose($handle);

        return $written;
    }

    private function ensureParentDirectory(string $path): void
    {
        $directory = dirname($path);
        if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
            throw new \RuntimeException('Unable to create directory: ' . $directory);
        }
    }

    /**
     * @param string[] $phrases
     * @param string[] $ignoredSources
     * @return string[]
     */
    private function filterIgnoredPhrases(array $phrases, array $ignoredSources): array
    {
        if ($ignoredSources === []) {
            return $phrases;
        }

        $ignoredLookup = array_fill_keys($ignoredSources, true);
        $filtered = [];

        foreach ($phrases as $phrase) {
            if (isset($ignoredLookup[$phrase])) {
                continue;
            }

            $filtered[] = $phrase;
        }

        return $filtered;
    }

    /**
     * @return array{0:string,1:bool}
     */
    private function resolveCsvPath(InputInterface $input, OutputInterface $output, string $path): array
    {
        if (!file_exists($path) || $input->getOption(self::OPTION_APPEND)) {
            return [$path, file_exists($path)];
        }

        if (!$input->isInteractive()) {
            throw new \RuntimeException('CSV already exists. Re-run with --append or provide a different --csv path.');
        }

        $helper = $this->getHelper('question');
        while (file_exists($path)) {
            $question = new ChoiceQuestion(
                'CSV file "' . $path . '" already exists. What do you want to do?',
                ['append', 'new path', 'cancel'],
                'append'
            );
            $choice = (string)$helper->ask($input, $output, $question);

            if ($choice === 'append') {
                return [$path, true];
            }

            if ($choice === 'new path') {
                $pathQuestion = new Question('Enter a new CSV path: ');
                $newPath = trim((string)$helper->ask($input, $output, $pathQuestion));
                if ($newPath !== '') {
                    $path = $newPath;
                }
                continue;
            }

            throw new \RuntimeException('Operation cancelled. Provide a different CSV path next time.');
        }

        return [$path, false];
    }
}
