<?php

declare(strict_types=1);

namespace NoahVet\Reef\Command\DataFixtures;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

#[AsCommand(
    name: 'reef:data-fixture:sort',
    description: 'Sorts all keys alphabetically in all YAML files under the given path',
)]
class SortDataFixtureCommand extends Command implements SortDataFixtureCommandInterface
{
    protected function configure(): void
    {
        $this
            ->addArgument(
                'path',
                InputArgument::REQUIRED,
                'File or directory to process (e.g., config/, config/openapi.yaml)',
            )
            ->addOption(
                'dry-run',
                null,
                InputOption::VALUE_NONE,
                'Displays which files would be modified without actually writing them',
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $path = $input->getArgument('path');
        $dryRun = $input->getOption('dry-run');

        if (!\file_exists($path)) {
            $output->writeln("<error>The path '{$path}' does not exist.</error>");

            return Command::FAILURE;
        }

        if (\is_file($path)) {
            $this->processFile($path, $dryRun, $output);
        } else {
            $this->processDirectory($path, $dryRun, $output);
        }

        return Command::SUCCESS;
    }

    private function processDirectory(string $directory, bool $dryRun, OutputInterface $output): void
    {
        $dirIterator = new \RecursiveDirectoryIterator($directory);
        $iterator = new \RecursiveIteratorIterator($dirIterator);
        $yamlFiles = new \RegexIterator($iterator, '/^.+\.(ya?ml)$/i', \RegexIterator::GET_MATCH);

        foreach ($yamlFiles as $files) {
            $file = $files[0];
            $this->processFile($file, $dryRun, $output);
        }
    }

    private function processFile(string $filePath, bool $dryRun, OutputInterface $output): void
    {
        if (!\is_readable($filePath)) {
            $output->writeln("<error>Unable to read file: {$filePath}</error>");

            return;
        }

        try {
            $data = Yaml::parseFile($filePath);
        } catch (ParseException $e) {
            $output->writeln("<error>YAML parse error in {$filePath}: {$e->getMessage()}</error>");

            return;
        }

        // Fichiers vides / null : on ne touche pas
        if (null === $data) {
            return;
        }

        $sorted = $this->sortKeysRecursive($data);

        // Dump YAML trié
        $newContent = Yaml::dump(
            $sorted,
            10,  // profondeur inline max
            2,   // indentation (2 espaces)
            Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK,
        );

        // 1) Fusion : "  -" + "\n" + "    id: ..."  =>  "  - id: ..."
        $lines = \explode("\n", $newContent);
        $fixedLines = [];
        $count = \count($lines);

        for ($i = 0; $i < $count; ++$i) {
            $line = $lines[$i];

            if (\preg_match('/^(\s*)-\s*$/', $line, $m) && isset($lines[$i + 1])) {
                $nextLine = $lines[$i + 1];
                $nextTrimmed = \ltrim($nextLine);

                if (\str_starts_with($nextTrimmed, 'id:')) {
                    $afterId = \substr($nextTrimmed, \strlen('id:')); // " xxx" ou ""

                    $fixedLines[] = $m[1].'- id:'.$afterId;
                    ++$i; // on saute la ligne suivante fusionnée

                    continue;
                }
            }

            $fixedLines[] = $line;
        }

        // 2) Ajouter une ligne vide entre chaque entrée de tableau
        //
        // Pour chaque niveau d'indentation, si on a déjà vu un "- ..."
        // et qu'on en revoit un autre au même indent, on insère une ligne vide avant.
        $spacedLines = [];
        /** @var array<int, bool> $seenListItemAtIndent */
        $seenListItemAtIndent = [];

        foreach ($fixedLines as $line) {
            $trim = \ltrim($line);

            // Ligne vide : on la pousse telle quelle
            if ('' === $trim) {
                $spacedLines[] = $line;
                continue;
            }

            $indent = \strlen($line) - \strlen($trim);
            $isListItem = 1 === \preg_match('/^-\s+/', $trim);

            if ($isListItem) {
                if (!empty($seenListItemAtIndent[$indent])) {
                    // On a déjà vu un item de liste à ce niveau → on insère une ligne vide
                    if (!empty($spacedLines)) {
                        $lastOut = \end($spacedLines);
                        if ('' !== \trim($lastOut)) {
                            $spacedLines[] = '';
                        }
                    }
                }

                $seenListItemAtIndent[$indent] = true;
            }

            $spacedLines[] = $line;
        }

        $newContent = \implode("\n", $spacedLines);

        $oldContent = \file_get_contents($filePath);

        if ($newContent === $oldContent) {
            return;
        }

        if ($dryRun) {
            $output->writeln("<comment>[DRY-RUN]</comment> {$filePath} would be modified.");
        } else {
            if (false === \file_put_contents($filePath, $newContent)) {
                $output->writeln("<error>Unable to write file: {$filePath}</error>");

                return;
            }

            $output->writeln("<info>Sorted keys:</info> {$filePath}");
        }
    }

    private function sortKeysRecursive(mixed $value): mixed
    {
        if (!\is_array($value)) {
            return $value;
        }

        if ($this->isList($value)) {
            foreach ($value as $k => $v) {
                $value[$k] = $this->sortKeysRecursive($v);
            }

            return $value;
        }

        $keys = \array_keys($value);

        \usort($keys, static function (string|int $a, string|int $b): int {
            $sa = (string) $a;
            $sb = (string) $b;

            $pa = 1;
            $pb = 1;

            if ('id' === $sa) {
                $pa = 0;
            } elseif ('' !== $sa && '_' === $sa[0]) {
                $pa = 2;
            }

            if ('id' === $sb) {
                $pb = 0;
            } elseif ('' !== $sb && '_' === $sb[0]) {
                $pb = 2;
            }

            if ($pa !== $pb) {
                return $pa <=> $pb;
            }

            return \strcasecmp($sa, $sb);
        });

        $sorted = [];
        foreach ($keys as $key) {
            $sorted[$key] = $this->sortKeysRecursive($value[$key]);
        }

        return $sorted;
    }

    /**
     * @param array<mixed, mixed> $array
     */
    private function isList(array $array): bool
    {
        return [] === $array || \array_is_list($array);
    }
}
