<?php

/**
 * Компілятор SCSS в CSS
 * Підтримка компіляції SCSS файлів для тем
 *
 * @package Flowaxy\Infrastructure\Compilers
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Compilers;

use Exception;
use Flowaxy\Contracts\Compilers\CompilerInterface;
use Flowaxy\Support\Helpers\FileHelper;
use Flowaxy\Support\Helpers\ResourceCleanupHelper;
use Flowaxy\Support\Helpers\UrlHelper;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Throwable;

use function dirname;
use function implode;
use function rtrim;
use const DS;

final class ScssCompiler implements CompilerInterface
{
    private string $scssDir;
    private string $cssDir;
    private string $themePath;
    private string $mainScssFile;
    private string $outputCssFile;

    /**
     * Конструктор
     *
     * @param string $themePath Шлях до папки теми
     * @param string $mainScssFile Головний файл SCSS (наприклад, main.scss)
     * @param string $outputCssFile Вихідний файл CSS
     */
    public function __construct(string $themePath, string $mainScssFile = 'assets/scss/main.scss', string $outputCssFile = 'assets/css/style.css')
    {
        $ds = defined('DS') ? DS : DIRECTORY_SEPARATOR;
        // Нормализуем путь используя DS разделитель
        $this->themePath = rtrim(str_replace(['/', '\\'], $ds, $themePath), $ds) . $ds;
        $this->mainScssFile = str_replace(['/', '\\'], $ds, $mainScssFile);
        $this->outputCssFile = str_replace(['/', '\\'], $ds, $outputCssFile);
        $this->scssDir = $this->themePath . dirname($this->mainScssFile) . $ds;
        $this->cssDir = $this->themePath . dirname($this->outputCssFile) . $ds;
    }

    /**
     * Перевірка наявності SCSS файлів в темі
     *
     * @return bool
     */
    public function hasScssFiles(): bool
    {
        $mainFile = $this->themePath . $this->mainScssFile;

        return FileHelper::exists($mainFile) && FileHelper::isReadable($mainFile);
    }

    /**
     * Компіляція SCSS в CSS (реалізація інтерфейсу)
     *
     * @param string $source Шлях до вихідного файлу
     * @param string $destination Шлях до цільового файлу
     * @return bool Успішність компіляції
     */
    public function compile(string $source, string $destination): bool
    {
        // Если путь относительный, добавляем $this->themePath
        // Используем DS разделитель для кроссплатформенности
        $ds = defined('DS') ? DS : DIRECTORY_SEPARATOR;
        $isAbsolute = str_starts_with($source, '/') || str_starts_with($source, $ds);

        $mainFile = $isAbsolute
            ? $source
            : $this->themePath . ltrim(str_replace(['/', '\\'], $ds, $source), $ds);

        if (! FileHelper::exists($mainFile) || ! FileHelper::isReadable($mainFile)) {
            error_log("SCSS Compiler: Головний SCSS файл не знайдено: " . $mainFile);
            return false;
        }

        // Если путь относительный, добавляем $this->themePath
        $isAbsoluteOutput = str_starts_with($destination, '/') || str_starts_with($destination, $ds);
        $outputFile = $isAbsoluteOutput
            ? $destination
            : $this->themePath . ltrim(str_replace(['/', '\\'], $ds, $destination), $ds);

        // Перевіряємо, чи потрібно компілювати
        if (FileHelper::exists($outputFile)) {
            // Перевіряємо, чи змінилися SCSS файли
            if ($this->isUpToDate($mainFile, $outputFile)) {
                return true; // Вже скомпільовано і актуально
            }
        }

        // Створюємо директорію для CSS, якщо не існує (используем dirname от полного пути)
        $outputDir = dirname($outputFile);
        if (! FileHelper::isDirectory($outputDir)) {
            if (! FileHelper::makeDirectory($outputDir, 0755, true)) {
                error_log("SCSS Compiler: Неможливо створити директорію CSS: " . $outputDir);
                return false;
            }
        }

        // Намагаємося використати scssphp, якщо доступний
        if ($this->compileWithScssphp($mainFile, $outputFile)) {
            error_log("SCSS Compiler: Успішно скомпільовано через scssphp. Output: " . $outputFile);
            return true;
        }

        // Намагаємося використати зовнішній процес (sass/node-sass)
        if ($this->compileWithExternalProcess($mainFile, $outputFile)) {
            error_log("SCSS Compiler: Успішно скомпільовано через зовнішній процес. Output: " . $outputFile);
            return true;
        }

        // Если компиляторы недоступны, создаем минимальный CSS файл с предупреждением
        $warningCss = "/* WARNING: SCSS compiler not available. Please install scssphp or sass/node-sass. */\n/* This file should be auto-generated from SCSS files in scss/ directory. */\n";
        if (FileHelper::put($outputFile, $warningCss) !== false) {
            error_log("SCSS Compiler: Компілятор недоступний, створено порожній CSS файл. Output: " . $outputFile . ", Main: " . $mainFile);
            return true; // Возвращаем true чтобы не блокировать загрузку страницы
        }

        error_log("SCSS Compiler: Компілятор недоступний і не вдалося створити CSS файл. Output: " . $outputFile . ", Main: " . $mainFile);
        return false;
    }

    /**
     * Компіляція з використанням scssphp
     *
     * @param string $inputFile Вхідний файл SCSS
     * @param string $outputFile Вихідний файл CSS
     * @return bool
     */
    private function compileWithScssphp(string $inputFile, string $outputFile): bool
    {
        // Пробуємо завантажити через автозавантажувач (основной способ)
        $compilerClass = 'ScssPhp\ScssPhp\Compiler';

        if (!class_exists($compilerClass)) {
            // Пробуємо підключити локальну копію scssphp (без composer)
            $ds = defined('DS') ? DS : DIRECTORY_SEPARATOR;
            $rootDir = defined('ROOT_DIR') ? ROOT_DIR : dirname(__DIR__, 4);
            $localPaths = [
                __DIR__ . $ds . 'scssphp' . $ds . 'scssphp' . $ds . 'src' . $ds . 'Compiler.php',
                __DIR__ . $ds . '..' . $ds . 'scssphp' . $ds . 'scssphp' . $ds . 'src' . $ds . 'Compiler.php',
                $rootDir . $ds . 'flowaxy' . $ds . 'Infrastructure' . $ds . 'Compilers' . $ds . 'scssphp' . $ds . 'scssphp' . $ds . 'src' . $ds . 'Compiler.php',
                $rootDir . $ds . 'flowaxy' . $ds . 'Infrastructure' . $ds . 'vendor' . $ds . 'scssphp' . $ds . 'scssphp' . $ds . 'src' . $ds . 'Compiler.php',
            ];
            foreach ($localPaths as $scssphpFile) {
                if (FileHelper::exists($scssphpFile)) {
                    require_once $scssphpFile;
                    break;
                }
            }

            // Пробуємо через spl_autoload_call (используем автозагрузчик системы)
            if (!class_exists($compilerClass) && function_exists('spl_autoload_call')) {
                try {
                    spl_autoload_call($compilerClass);
                } catch (Exception $e) {
                    error_log("SCSS Compiler: Помилка автозавантаження scssphp: " . $e->getMessage());
                }
            }
        }

        if (! class_exists($compilerClass)) {
            error_log("SCSS Compiler: ScssPhp\\ScssPhp\\Compiler клас не знайдено через автозавантажувач");
            return false;
        }

        try {
            // Створюємо екземпляр через змінну для уникнення помилок статичного аналізу
            $compiler = new $compilerClass();
            // Используем dirname от inputFile для правильного пути импорта
            $scssImportDir = dirname($inputFile);
            // Добавляем оба пути для поддержки @import из разных мест
            $importPaths = array_unique([$scssImportDir, $this->scssDir]);
            $compiler->setImportPaths($importPaths);

            $scssContent = FileHelper::get($inputFile);
            if ($scssContent === false) {
                return false;
            }

            $cssContent = $compiler->compileString($scssContent)->getCss();

            if (FileHelper::put($outputFile, $cssContent) === false) {
                error_log("SCSS Compiler: Неможливо записати вихідний файл: " . $outputFile);
                return false;
            }

            return true;
        } catch (Exception $e) {
            error_log("SCSS Compiler: Помилка компіляції scssphp: " . $e->getMessage());
            return false;
        }
    }

    /**
     * Компіляція з використанням зовнішнього процесу (sass/node-sass)
     *
     * @param string $inputFile Вхідний файл SCSS
     * @param string $outputFile Вихідний файл CSS
     * @return bool
     */
    private function compileWithExternalProcess(string $inputFile, string $outputFile): bool
    {
        // Пробуємо sass (Dart Sass)
        $sassCommand = 'sass';
        if ($this->commandExists($sassCommand)) {
            $command = sprintf(
                '%s "%s" "%s" --no-source-map --style compressed 2>&1',
                escapeshellcmd($sassCommand),
                escapeshellarg($inputFile),
                escapeshellarg($outputFile)
            );

            $output = [];
            $returnCode = 0;
            exec($command, $output, $returnCode);

            if ($returnCode === 0 && FileHelper::exists($outputFile)) {
                return true;
            }

            error_log("SCSS Compiler: Помилка виконання команди sass: " . implode("\n", $output));
        }

        // Пробуємо node-sass
        $nodeSassCommand = 'node-sass';
        if ($this->commandExists($nodeSassCommand)) {
            $command = sprintf(
                '%s "%s" --output "%s" --output-style compressed --source-map false 2>&1',
                escapeshellcmd($nodeSassCommand),
                escapeshellarg($inputFile),
                escapeshellarg(dirname($outputFile))
            );

            $output = [];
            $returnCode = 0;
            exec($command, $output, $returnCode);

            $expectedOutput = dirname($outputFile) . '/' . basename($inputFile, '.scss') . '.css';
            if ($returnCode === 0 && FileHelper::exists($expectedOutput)) {
                // Перейменовуємо, якщо потрібно
                if ($expectedOutput !== $outputFile) {
                    FileHelper::move($expectedOutput, $outputFile);
                }

                return true;
            }

            error_log("SCSS Compiler: Помилка виконання команди node-sass: " . implode("\n", $output));
        }

        return false;
    }

    /**
     * Перевірка, актуальні чи скомпільовані файли
     *
     * @param string $mainFile Головний SCSS файл
     * @param string $outputFile Вихідний CSS файл
     * @return bool
     */
    private function isUpToDate(string $mainFile, string $outputFile): bool
    {
        if (! FileHelper::exists($outputFile)) {
            return false;
        }

        $mainFileTime = FileHelper::lastModified($mainFile);
        $outputFileTime = FileHelper::lastModified($outputFile);

        if ($mainFileTime === false || $outputFileTime === false) {
            return false;
        }

        if ($mainFileTime > $outputFileTime) {
            return false;
        }

        // Перевіряємо всі SCSS файли в директорії
        $scssFiles = $this->getAllScssFiles($this->scssDir);
        foreach ($scssFiles as $file) {
            if (FileHelper::exists($file)) {
                $fileTime = FileHelper::lastModified($file);
                if ($fileTime !== false && $fileTime > $outputFileTime) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Отримання всіх SCSS файлів в директорії рекурсивно
     *
     * @param string $dir Директорія
     * @return array<string>
     */
    private function getAllScssFiles(string $dir): array
    {
        $files = [];

        if (! FileHelper::isDirectory($dir)) {
            return $files;
        }

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::LEAVES_ONLY
        );

        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getExtension() === 'scss') {
                $files[] = $file->getPathname();
            }
        }

        return $files;
    }

    /**
     * Перевірка існування команди в системі
     *
     * @param string $command Команда
     * @return bool
     */
    private function commandExists(string $command): bool
    {
        $whereIsCommand = (PHP_OS === 'WINNT') ? 'where' : 'which';

        $process = proc_open(
            "$whereIsCommand $command",
            [
                0 => ['pipe', 'r'],
                1 => ['pipe', 'w'],
                2 => ['pipe', 'w'],
            ],
            $pipes
        );

        if ($process === false) {
            return false;
        }

        $output = stream_get_contents($pipes[1]);
        proc_close($process);

        return ! empty($output);
    }

    /**
     * Отримання URL скомпільованого CSS файла
     *
     * @return string|null
     */
    public function getCssUrl(): ?string
    {
        $outputFile = $this->themePath . $this->outputCssFile;

        if (! FileHelper::exists($outputFile)) {
            return null;
        }

        $themeSlug = basename($this->themePath);
        // Використовуємо UrlHelper для отримання актуального URL з правильним протоколом
        if (class_exists(UrlHelper::class)) {
            return UrlHelper::site('/themes/' . $themeSlug . '/' . $this->outputCssFile);
        }
        // Fallback на константу, якщо UrlHelper не доступний
        $siteUrl = defined('SITE_URL') ? SITE_URL : '';

        return $siteUrl . '/themes/' . $themeSlug . '/' . $this->outputCssFile;
    }

    /**
     * Очищення скомпільованих файлів
     *
     * @return bool
     */
    public function clean(): bool
    {
        $outputFile = $this->themePath . $this->outputCssFile;

        if (FileHelper::exists($outputFile)) {
            return FileHelper::delete($outputFile);
        }

        return true;
    }

    /**
     * Перевірка необхідності компіляції (реалізація інтерфейсу)
     *
     * @param string $source Шлях до вихідного файлу
     * @param string $destination Шлях до цільового файлу
     * @return bool Чи потрібна компіляція
     */
    public function needsCompilation(string $source, string $destination): bool
    {
        if (!FileHelper::exists($destination)) {
            return true;
        }

        return !$this->isUpToDate($source, $destination);
    }

    /**
     * Отримання підтримуваних розширень (реалізація інтерфейсу)
     *
     * @return array<int, string> Масив розширень
     */
    public function getSupportedExtensions(): array
    {
        return ['scss', 'sass'];
    }

    /**
     * Компіляція SCSS в CSS (старий метод для зворотної сумісності)
     *
     * @param bool $force Примусова перекомпіляція
     * @return bool Успішність компіляції
     */
    public function compileTheme(bool $force = false): bool
    {
        $mainFile = $this->themePath . $this->mainScssFile;
        $outputFile = $this->themePath . $this->outputCssFile;

        if ($force || $this->needsCompilation($mainFile, $outputFile)) {
            return $this->compile($mainFile, $outputFile);
        }

        return true;
    }
}
