<?php

namespace Ignite\Core\Listeners\ThemeScaffolding;

use Ignite\Theme\Exception\ThemeNotFoundException;
use Illuminate\Filesystem\Filesystem;
use Symfony\Component\Console\Output\OutputInterface;

class ModernThemeScaffolder
{
    /**
     * The folder under which themes are located.
     *
     * @var string
     */
    protected $folder = 'themes';

    /**
     * The manifest filename that declares what to scaffold.
     *
     * @var string
     */
    protected $manifestFile = '.theme-scaffold.json';

    /**
     * The stubs directory name within the parent theme.
     *
     * @var string
     */
    protected $stubsDir = '.stubs';

    /**
     * Run the modern scaffold strategy.
     *
     * @param  string $name
     * @param  string $parent
     * @param  string $path
     * @param  OutputInterface $output
     * @return void
     */
    public function scaffold(string $name, string $parent, string $path, OutputInterface $output): void
    {
        $parent = $parent ?: 'default';

        $manifest = $this->loadManifest($parent, $output);
        $this->createDirectories($path, $manifest);
        $this->createPlaceholderFiles($path, $manifest);

        $output->writeln("Copying files from the {$parent} theme...");

        $this->copyStarterFiles($name, $parent, $manifest);
        $this->copyStarterDirectories($name, $parent, $manifest);
        $this->generateStubs($name, $parent, $manifest);
    }

    /**
     * Create the modern theme directory structure from manifest.
     *
     * @param  string $path
     * @param  array $manifest
     * @return void
     */
    protected function createDirectories(string $path, array $manifest): void
    {
        $emptyDirectories = $manifest['emptyDirectories'] ?? [];

        foreach ($emptyDirectories as $folder) {
            $this->files()->ensureDirectoryExists($path . DIRECTORY_SEPARATOR . $this->normalizePath($folder));
        }
    }

    /**
     * Create configured placeholder files for directories that Git must retain.
     *
     * Placeholders are declared separately from empty directories so build
     * output folders such as dist remain empty and are not published as assets.
     *
     * @param  string $path
     * @param  array $manifest
     * @return void
     */
    protected function createPlaceholderFiles(string $path, array $manifest): void
    {
        $placeholderFiles = $manifest['placeholderFiles'] ?? [];
        $filesystem = $this->files();

        foreach ($placeholderFiles as $file) {
            $placeholder = $path . DIRECTORY_SEPARATOR . $this->normalizePath($file);

            $filesystem->ensureDirectoryExists(dirname($placeholder));

            // Do not replace a real file if a future manifest uses the same path.
            if (! $filesystem->exists($placeholder)) {
                $filesystem->put($placeholder, '');
            }
        }
    }

    /**
     * Load the scaffold manifest from the parent theme.
     * Falls back to minimal defaults if manifest is missing, with a warning.
     *
     * @param  string $parent
     * @param  OutputInterface $output
     * @return array
     */
    protected function loadManifest(string $parent, OutputInterface $output): array
    {
        $starterPath = $this->getStarterThemePath($parent);
        $manifestPath = $starterPath . DIRECTORY_SEPARATOR . $this->manifestFile;

        if (! $this->files()->exists($manifestPath)) {
            $output->writeln("<comment>Warning: {$parent} theme is missing {$this->manifestFile}. Using minimal scaffold defaults.</comment>");

            // Minimal fallback for backwards compatibility
            return [
                'emptyDirectories' => [
                    'dist',
                    'views',
                ],
                'copyFiles' => [
                    'composer.json',
                    'package.json',
                ],
                'copyDirectories' => [],
                'placeholderFiles' => [
                    'views/.gitkeep',
                ],
                'stubs' => [],
            ];
        }

        return json_decode($this->files()->get($manifestPath), true) ?? [];
    }

    /**
     * Copy starter files from the parent theme based on manifest.
     *
     * @param  string $name
     * @param  string $parent
     * @param  array $manifest
     * @return void
     */
    protected function copyStarterFiles(string $name, string $parent, array $manifest): void
    {
        $filesystem = $this->files();
        $starterPath = $this->getStarterThemePath($parent);
        $files = $manifest['copyFiles'] ?? [];

        foreach ($files as $file) {
            $source = $starterPath . DIRECTORY_SEPARATOR . $this->normalizePath($file);
            $destination = $this->getPath($name, $file);

            if (! $filesystem->exists($source)) {
                continue;
            }

            $filesystem->ensureDirectoryExists(dirname($destination));
            $filesystem->copy($source, $destination);
        }
    }

    /**
     * Copy starter directories from the parent theme based on manifest.
     *
     * @param  string $name
     * @param  string $parent
     * @param  array $manifest
     * @return void
     */
    protected function copyStarterDirectories(string $name, string $parent, array $manifest): void
    {
        $filesystem = $this->files();
        $starterPath = $this->getStarterThemePath($parent);
        $directories = $manifest['copyDirectories'] ?? [];

        foreach ($directories as $directory) {
            $source = $starterPath . DIRECTORY_SEPARATOR . $this->normalizePath($directory);
            $destination = $this->getPath($name, $directory);

            if (! $filesystem->isDirectory($source)) {
                continue;
            }

            $filesystem->copyDirectory($source, $destination);
        }
    }

    /**
     * Generate stub files from templates in the parent theme.
     *
     * @param  string $name
     * @param  string $parent
     * @param  array $manifest
     * @return void
     */
    protected function generateStubs(string $name, string $parent, array $manifest): void
    {
        $filesystem = $this->files();
        $stubs = $manifest['stubs'] ?? [];
        $starterPath = $this->getStarterThemePath($parent);
        $stubsPath = $starterPath . DIRECTORY_SEPARATOR . $this->stubsDir;

        foreach ($stubs as $filePath => $shouldGenerate) {
            if (! $shouldGenerate) {
                continue;
            }

            // Resolve stub template file: .stubs/src/js/script.js.stub
            $stubTemplatePath = $stubsPath . DIRECTORY_SEPARATOR . $this->normalizePath($filePath) . '.stub';

            if (! $filesystem->exists($stubTemplatePath)) {
                // Skip if stub template is not found
                continue;
            }

            $destination = $this->getPath($name, $filePath);
            $filesystem->ensureDirectoryExists(dirname($destination));

            // Calculate relative path from child theme to vendor parent theme
            $relativePath = $this->calculateRelativeImportPath($filePath, $parent);

            // Load template and replace placeholder
            $stubContent = $filesystem->get($stubTemplatePath);
            $stubContent = str_replace('{PARENT_IMPORT_PATH}', $relativePath, $stubContent);

            $filesystem->put($destination, $stubContent);
        }
    }

    /**
     * Calculate the relative import path from child theme to parent theme file.
     *
     * @param  string $filePath
     * @param  string $parent
     * @return string
     */
    protected function calculateRelativeImportPath(string $filePath, string $parent): string
    {
        // From: themes/<child-name>/src/js/...
        // To:   vendor/ignite/themes/<parent>/src/js/...

        $depth = count(array_filter(explode(DIRECTORY_SEPARATOR, $filePath), fn($p) => $p !== ''));
        $vendorPath = implode('/', array_fill(0, $depth, '..')) . "/vendor/ignite/themes/{$parent}/{$filePath}";

        return $vendorPath;
    }

    /**
     * Resolve the starter theme path for the given parent theme name.
     *
     * @param  string $parent
     * @return string
     */
    protected function getStarterThemePath(string $parent): string
    {
        try {
            $path = app('theme')->get($parent)->path();
        } catch (ThemeNotFoundException $e) {
            throw new \InvalidArgumentException("Missing starter theme `{$parent}`.", 0, $e);
        }

        if (! $this->files()->exists($path)) {
            throw new \InvalidArgumentException("Missing starter theme directory: {$path}");
        }

        return $path;
    }

    /**
     * Get the path to a directory in context of the current theme.
     *
     * @param  string $name
     * @param  string|null $path
     * @return string
     */
    protected function getPath(string $name, ?string $path = null): string
    {
        return base_path(
            $this->folder . DIRECTORY_SEPARATOR .
            $name .
            (is_null($path) ? '' : DIRECTORY_SEPARATOR . $this->normalizePath($path))
        );
    }

    /**
     * Normalize the path for use across different OSs.
     *
     * @param  string $path
     * @return string
     */
    protected function normalizePath(string $path): string
    {
        return str_replace('/', DIRECTORY_SEPARATOR, $path);
    }

    /**
     * The filesystem implementation.
     *
     * @return Filesystem
     */
    protected function files(): Filesystem
    {
        return app('files');
    }
}
