<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiTheme\Command;

use CpCreation\VitiTheme\Theme\ThemeRegistry;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'viti-theme:assets:install',
    description: 'Installs public assets from registered Viti themes.',
)]
final class InstallThemeAssetsCommand extends Command
{
    public function __construct(
        private readonly ThemeRegistry $themeRegistry,
        private readonly string $projectDir,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->addOption('symlink', null, InputOption::VALUE_NONE, 'Create symlinks instead of copying files.');
        $this->addOption('relative', null, InputOption::VALUE_NONE, 'Create relative symlinks when used with --symlink.');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetRoot = $this->projectDir.'/public/themes';

        if (!is_dir($targetRoot) && !mkdir($targetRoot, 0775, true) && !is_dir($targetRoot)) {
            throw new \RuntimeException(\sprintf('Unable to create theme assets directory "%s".', $targetRoot));
        }

        foreach ($this->themeRegistry->all() as $theme) {
            $sourcePath = $theme->publicPath();

            if (null === $sourcePath || !is_dir($sourcePath)) {
                continue;
            }

            $targetPath = $targetRoot.'/'.$theme->name;
            $this->removePath($targetPath);

            if (true === $input->getOption('symlink')) {
                $symlinkTarget = true === $input->getOption('relative')
                    ? $this->relativePath(\dirname($targetPath), $sourcePath)
                    : $sourcePath;

                if (!symlink($symlinkTarget, $targetPath)) {
                    throw new \RuntimeException(\sprintf('Unable to create symlink "%s" to "%s".', $targetPath, $symlinkTarget));
                }
            } else {
                $this->copyDirectory($sourcePath, $targetPath);
            }

            $output->writeln(\sprintf('Installed assets for theme "%s".', $theme->name));
        }

        return Command::SUCCESS;
    }

    private function copyDirectory(string $sourcePath, string $targetPath): void
    {
        if (!is_dir($targetPath) && !mkdir($targetPath, 0775, true) && !is_dir($targetPath)) {
            throw new \RuntimeException(\sprintf('Unable to create directory "%s".', $targetPath));
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($sourcePath, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::SELF_FIRST,
        );

        foreach ($iterator as $item) {
            $target = $targetPath.'/'.mb_ltrim($iterator->getSubPathName(), '/');

            if ($item->isDir()) {
                if (!is_dir($target) && !mkdir($target, 0775, true) && !is_dir($target)) {
                    throw new \RuntimeException(\sprintf('Unable to create directory "%s".', $target));
                }

                continue;
            }

            if (!copy($item->getPathname(), $target)) {
                throw new \RuntimeException(\sprintf('Unable to copy "%s" to "%s".', $item->getPathname(), $target));
            }
        }
    }

    private function removePath(string $path): void
    {
        if (is_link($path) || is_file($path)) {
            unlink($path);

            return;
        }

        if (!is_dir($path)) {
            return;
        }

        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST,
        );

        foreach ($iterator as $item) {
            if ($item->isDir() && !$item->isLink()) {
                rmdir($item->getPathname());

                continue;
            }

            unlink($item->getPathname());
        }

        rmdir($path);
    }

    private function relativePath(string $fromDirectory, string $toPath): string
    {
        $fromParts = explode('/', mb_trim($this->normalizePath($fromDirectory), '/'));
        $toParts = explode('/', mb_trim($this->normalizePath($toPath), '/'));

        while ([] !== $fromParts && [] !== $toParts && $fromParts[0] === $toParts[0]) {
            array_shift($fromParts);
            array_shift($toParts);
        }

        return str_repeat('../', \count($fromParts)).implode('/', $toParts);
    }

    private function normalizePath(string $path): string
    {
        return str_replace('\\', '/', realpath($path) ?: $path);
    }
}
