<?php
/**
 * Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
 *
 * This file is part of a Moko Consulting project.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/**
 * Template install/update/uninstall script.
 * Joomla calls the methods in this class automatically during template
 * install, update, and uninstall via the <scriptfile> element in
 * templateDetails.xml.
 *
 * On first install, detects MokoCassiopeia and migrates template styles,
 * parameters, menu assignments, and user files automatically.
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Log\Log;

class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface
{
    private const MIN_PHP = '8.1.0';
    private const MIN_JOOMLA = '4.4.0';

    private const OLD_NAME = 'mokocassiopeia';
    private const NEW_NAME = 'mokoonyx';
    private const OLD_DISPLAY = 'MokoCassiopeia';
    private const NEW_DISPLAY = 'MokoOnyx';

    public function preflight(string $type, InstallerAdapter $parent): bool
    {
        if (version_compare(PHP_VERSION, self::MIN_PHP, '<')) {
            Factory::getApplication()->enqueueMessage(
                sprintf('MokoOnyx requires PHP %s or later. You are running PHP %s.', self::MIN_PHP, PHP_VERSION),
                'error'
            );
            return false;
        }

        if (version_compare(JVERSION, self::MIN_JOOMLA, '<')) {
            Factory::getApplication()->enqueueMessage(
                sprintf('MokoOnyx requires Joomla %s or later. You are running Joomla %s.', self::MIN_JOOMLA, JVERSION),
                'error'
            );
            return false;
        }

        return true;
    }

    public function install(InstallerAdapter $parent): bool
    {
        $this->logMessage('MokoOnyx template installed.');
        return true;
    }

    public function update(InstallerAdapter $parent): bool
    {
        $this->logMessage('MokoOnyx template updated.');

        $synced = $this->syncCustomVariables($parent);
        if ($synced > 0) {
            Factory::getApplication()->enqueueMessage(
                sprintf(
                    'MokoOnyx: %d new CSS variable(s) were added to your custom palette files. '
                    . 'Review them in your light.custom.css and/or dark.custom.css to customise the new defaults.',
                    $synced
                ),
                'notice'
            );
        }

        return true;
    }

    public function uninstall(InstallerAdapter $parent): bool
    {
        $this->logMessage('MokoOnyx template uninstalled.');
        return true;
    }

    public function postflight(string $type, InstallerAdapter $parent): bool
    {
        // On install or update: migrate from MokoCassiopeia if it exists
        if ($type === 'install' || $type === 'update') {
            $this->migrateFromCassiopeia();
        }

        return true;
    }

    /**
     * Detect MokoCassiopeia and create matching MokoOnyx styles with the same params.
     * Creates a MokoOnyx style copy for each MokoCassiopeia style.
     */
    private function migrateFromCassiopeia(): void
    {
        $db = Factory::getDbo();

        // Get all MokoCassiopeia styles
        $query = $db->getQuery(true)
            ->select('*')
            ->from('#__template_styles')
            ->where($db->quoteName('template') . ' = ' . $db->quote(self::OLD_NAME))
            ->where($db->quoteName('client_id') . ' = 0');
        $oldStyles = $db->setQuery($query)->loadObjectList();

        if (empty($oldStyles)) {
            $this->logMessage('No MokoCassiopeia styles found — fresh install.');
            return;
        }

        $this->logMessage('MokoCassiopeia detected — creating ' . count($oldStyles) . ' matching MokoOnyx style(s).');

        // Get the installer-created default MokoOnyx style (to apply params to it)
        $query = $db->getQuery(true)
            ->select('id')
            ->from('#__template_styles')
            ->where($db->quoteName('template') . ' = ' . $db->quote(self::NEW_NAME))
            ->where($db->quoteName('client_id') . ' = 0')
            ->order($db->quoteName('id') . ' ASC');
        $defaultOnyxId = (int) $db->setQuery($query, 0, 1)->loadResult();

        $firstStyle = true;

        foreach ($oldStyles as $oldStyle) {
            $newTitle = str_replace(self::OLD_DISPLAY, self::NEW_DISPLAY, $oldStyle->title);
            $newTitle = str_replace(self::OLD_NAME, self::NEW_NAME, $newTitle);

            $params = is_string($oldStyle->params)
                ? str_replace(self::OLD_NAME, self::NEW_NAME, $oldStyle->params)
                : $oldStyle->params;

            if ($firstStyle && $defaultOnyxId) {
                // Update the installer-created default style with the first MokoCassiopeia style's params
                $update = $db->getQuery(true)
                    ->update('#__template_styles')
                    ->set($db->quoteName('params') . ' = ' . $db->quote($params))
                    ->set($db->quoteName('title') . ' = ' . $db->quote($newTitle))
                    ->where('id = ' . $defaultOnyxId);
                $db->setQuery($update)->execute();

                // Set as default if MokoCassiopeia was default
                if ($oldStyle->home == 1) {
                    $db->setQuery(
                        $db->getQuery(true)
                            ->update('#__template_styles')
                            ->set($db->quoteName('home') . ' = 1')
                            ->where('id = ' . $defaultOnyxId)
                    )->execute();

                    $db->setQuery(
                        $db->getQuery(true)
                            ->update('#__template_styles')
                            ->set($db->quoteName('home') . ' = 0')
                            ->where('id = ' . (int) $oldStyle->id)
                    )->execute();

                    $this->logMessage('Set MokoOnyx as default site template.');
                }

                $this->logMessage("Updated default MokoOnyx style with params: {$newTitle}");
                $firstStyle = false;
                continue;
            }

            // For additional styles: create new MokoOnyx style copies
            $newStyle = clone $oldStyle;
            unset($newStyle->id);
            $newStyle->template = self::NEW_NAME;
            $newStyle->title = $newTitle;
            $newStyle->home = 0;
            $newStyle->params = $params;

            try {
                $db->insertObject('#__template_styles', $newStyle, 'id');
                $this->logMessage("Created MokoOnyx style: {$newTitle}");
            } catch (\Throwable $e) {
                $this->logMessage("Failed to create style {$newTitle}: " . $e->getMessage(), 'warning');
            }
        }

        // 2. Copy user files (custom themes, user.css, user.js)
        $this->copyUserFiles();

        // 3. Notify admin
        Factory::getApplication()->enqueueMessage(
            '<strong>MokoOnyx has been installed as a replacement for MokoCassiopeia.</strong><br>'
            . 'Your template settings and custom files have been migrated automatically. '
            . 'MokoOnyx is now your active site template. '
            . 'You can safely uninstall MokoCassiopeia from Extensions &rarr; Manage.',
            'success'
        );
    }

    /**
     * Copy user-specific files from MokoCassiopeia media to MokoOnyx media.
     */
    private function copyUserFiles(): void
    {
        $oldMedia = JPATH_ROOT . '/media/templates/site/' . self::OLD_NAME;
        $newMedia = JPATH_ROOT . '/media/templates/site/' . self::NEW_NAME;

        if (!is_dir($oldMedia) || !is_dir($newMedia)) {
            return;
        }

        $userFiles = [
            'css/theme/light.custom.css',
            'css/theme/dark.custom.css',
            'css/theme/light.custom.min.css',
            'css/theme/dark.custom.min.css',
            'css/user.css',
            'css/user.min.css',
            'js/user.js',
            'js/user.min.js',
        ];

        $copied = 0;
        foreach ($userFiles as $relPath) {
            $src = $oldMedia . '/' . $relPath;
            $dst = $newMedia . '/' . $relPath;
            if (is_file($src) && !is_file($dst)) {
                $dstDir = dirname($dst);
                if (!is_dir($dstDir)) {
                    mkdir($dstDir, 0755, true);
                }
                copy($src, $dst);
                $copied++;
            }
        }

        if ($copied > 0) {
            $this->logMessage("Copied {$copied} user file(s) from MokoCassiopeia.");
        }
    }

    private function syncCustomVariables(InstallerAdapter $parent): int
    {
        $templateDir = $parent->getParent()->getPath('source');
        $syncScript = $templateDir . '/sync_custom_vars.php';

        if (!is_file($syncScript)) {
            $this->logMessage('CSS variable sync script not found at: ' . $syncScript, 'warning');
            return 0;
        }

        require_once $syncScript;

        if (!class_exists('MokoCssVarSync')) {
            $this->logMessage('MokoCssVarSync class not found after loading script.', 'warning');
            return 0;
        }

        try {
            $results = MokoCssVarSync::run(JPATH_ROOT);

            $totalAdded = 0;
            foreach ($results as $filePath => $result) {
                $totalAdded += count($result['added']);
                if (!empty($result['added'])) {
                    $this->logMessage(sprintf('CSS sync: added %d variable(s) to %s', count($result['added']), basename($filePath)));
                }
            }

            return $totalAdded;
        } catch (\Throwable $e) {
            $this->logMessage('CSS variable sync failed: ' . $e->getMessage(), 'error');
            return 0;
        }
    }

    private function logMessage(string $message, string $priority = 'info'): void
    {
        $priorities = [
            'info'    => Log::INFO,
            'warning' => Log::WARNING,
            'error'   => Log::ERROR,
        ];

        Log::addLogger(
            ['text_file' => 'mokoonyx.log.php'],
            Log::ALL,
            ['mokoonyx']
        );

        Log::add($message, $priorities[$priority] ?? Log::INFO, 'mokoonyx');
    }
}
