<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Admin;

use BookiFlex\Core\Admin\AssetContext;
use BookiFlex\Core\Container\ServiceContainer;
use BookiFlex\Core\Contract\PluginInterface;
use BookiFlex\Core\I18n\TranslationLoader;
use Psr\Log\LoggerInterface;

defined( 'ABSPATH' ) || exit;

abstract class AbstractAssetsManager
{
    protected PluginInterface $plugin;
    protected ServiceContainer $container;

    protected ?array $manifest = null;
    protected array $bookiFlexData = [
        'admin' => [],
        'frontend' => []
    ];
    protected array $inlineScripts = [
        'admin'    => [],
        'frontend' => [],
    ];
	protected TranslationLoader $translationLoader;
	protected ?LoggerInterface $logger;

	public function __construct(
        PluginInterface $plugin,
	    ?LoggerInterface $logger = null,
    ) {
        $this->plugin = $plugin;
        $this->container = $plugin->getContainer();
        $this->translationLoader = new TranslationLoader($plugin->getPluginDir());
        $this->initHooks();
		$this->logger = $logger;
	}

    protected function initHooks(): void
    {
        $this->plugin->addAction('init', [$this, 'registerBlockAssets']);
        $this->plugin->addAction('admin_enqueue_scripts', [$this, 'enqueueAdminAssets']);
        $this->plugin->addAction('wp_enqueue_scripts', [$this, 'enqueueFrontendAssets']);

        // CSS tokens in <head> so web components see them on first render
        $this->plugin->addAction('wp_head', [$this, 'printFrontendHead']);

        // Modules (type="module") in footer
        $this->plugin->addAction('wp_print_footer_scripts', [$this, 'printFrontendModules']);
        $this->plugin->addAction('admin_print_footer_scripts', [$this, 'printAdminModules']);

        // Finalize BookiFlex after all registrations
        $this->plugin->addAction('admin_enqueue_scripts', [$this, 'finalizeAdminBookiFlexData'], 999);
        $this->plugin->addAction('wp_enqueue_scripts', [$this, 'finalizeFrontendBookiFlexData'], 999);
    }

    public function registerBlockAssets(): void
    {
        wp_register_script(
            'bookiflex-search-form',
            $this->assetUrl($this->asset('prod', 'vendor', 'search-widget', 'js')),
            [],
            $this->plugin->getVersion(),
            false
        );
    }

    abstract public function enqueueAdminAssets(): void;

    protected function isBookiFlexAdminPage(): bool
    {
        $screen = get_current_screen();
        if (!$screen) {
            return false;
        }
        return str_contains($screen->id, 'bookiflex')
            || str_contains($screen->post_type ?? '', 'bflex');
    }

    abstract public function enqueueFrontendAssets(): void;

    public function printFrontendHead(): void {}

    abstract public function printFrontendModules(): void;

    abstract public function printAdminModules(): void;

	protected function enqueueModuleScript(string $handle, string $src, array $deps = []): void
	{
		wp_enqueue_script_module($handle, $src, $deps, $this->plugin->getVersion());
	}

    protected function enqueueScript(string $handle, string $src = '', array $deps = [], bool $inFooter = false, array $args = []): void
    {
        wp_enqueue_script($handle, $src, $deps, $this->plugin->getVersion(), [
			'in_footer' => $inFooter,
	        ...$args
        ]);
    }

    protected function enqueueStyle(string $handle, string $src, array $deps = []): void
    {
        wp_enqueue_style($handle, $src, $deps, $this->plugin->getVersion());
    }

    /**
     * Get and cache manifest with multi-manifest loading
     */
    protected function getManifest(): array
    {
        if ($this->manifest === null) {
            $this->manifest = $this->loadManifestFile('manifest.json');

            if (empty($this->manifest)) {
                return [];
            }

            $this->mergeAdditionalManifests();
        }

        return $this->manifest;
    }

    /**
     * Load and parse a manifest file from the assets directory
     */
    protected function loadManifestFile(string $filename): array
    {
        $manifestPath = $this->plugin->getPluginDir() . 'assets/' . $filename;

        if (!file_exists($manifestPath)) {
            $this->logger?->warning('BookiFlex: manifest file not found', ['path' => $manifestPath]);
            return [];
        }

        $content = file_get_contents($manifestPath);
        $data = json_decode($content, true);

        if ($data === null) {
            $this->logger?->warning('BookiFlex: Failed to parse manifest file', ['path' => $manifestPath]);
            return [];
        }

        return $data;
    }

    /**
     * Merge additional manifests (dev, premium) — no-op in base, override in child
     */
    protected function mergeAdditionalManifests(): void
    {
    }

    /**
     * Get value from manifest by nested keys
     * Example: asset('prod', 'admin', 'js')
     */
    protected function asset(string ...$keys): string
    {
        $manifest = $this->getManifest();
        $value = $manifest;

        foreach ($keys as $key) {
            if (!isset($value[$key])) {
                $this->logger?->warning('BookiFlex: Asset key not found in manifest', ['keys' => $keys]);
                return '';
            }
            $value = $value[$key];
        }

        return is_string($value) ? $value : '';
    }

    /**
     * Build full URL for asset
     */
    protected function assetUrl(string $path): string
    {
        if (empty($path)) {
            return '';
        }

        $pluginUrl = $this->container->get('plugin_url');
        return $pluginUrl . '/' . $path;
    }

    /**
     * Add raw inline JavaScript to be output after the bookiflex-vue script.
     * Use this for JS functions and other non-JSON-serializable content
     * that cannot be passed via addBookiFlexData / wp_localize_script.
     *
     * @param string $script Raw JavaScript (no <script> tags)
     * @param AssetContext $context Context (admin or frontend)
     */
    public function addInlineScript(string $script, AssetContext $context): void
    {
        $this->inlineScripts[$context->value][] = $script;
    }

    /**
     * Load JSON translations for script modules and inject via inline script.
     *
     * @param string[] $assetPaths Asset paths relative to plugin dir (from manifest)
     * @param AssetContext $context Context (admin or frontend)
     */
    public function loadTranslationsFor(array $assetPaths, AssetContext $context, ?string $locale = null): void
    {
        $script = $this->translationLoader->getInlineScript($assetPaths, $locale);
        if (!empty($script)) {
            $this->addInlineScript($script, $context);
        }
    }

    /**
     * Load translations for an extension by its manifest name.
     */
    public function loadExtensionTranslations(string $extensionName, AssetContext $context): void
    {
        $jsPath = $this->asset('prod', 'extensions', $extensionName, 'js');
        if (!empty($jsPath)) {
            $this->loadTranslationsFor([$jsPath], $context);
        }
    }

    /**
     * Enqueue extension assets as a Script Module (type="module", deferred).
     *
     * Use this instead of enqueueExtensionAssets() when the extension JS
     * should run as a Script Module — same pattern as the admin app.
     * Call loadExtensionTranslations() separately for i18n support.
     *
     * @param string $extensionName Extension name from manifest.json
     * @param array $options Options:
     *   - context: AssetContext - loading context (required)
     *   - data: array - data for BookiFlex.extensions.{name}
     */
    public function enqueueExtensionModule(string $extensionName, array $options = []): void
    {
        $manifest = $this->getManifest();
        if (!isset($manifest['prod']['extensions'][$extensionName])) {
            $this->logger?->warning('BookiFlex: Extension not found in manifest', ['extension' => $extensionName]);
            return;
        }

        $extension = $manifest['prod']['extensions'][$extensionName];
        $handle    = 'bookiflex-ext-' . $extensionName;
        $context   = $options['context'] ?? AssetContext::ADMIN;

        if (isset($extension['js'])) {
            $this->enqueueModuleScript($handle, $this->assetUrl($extension['js']));
        }

        if (isset($extension['css'])) {
            $this->enqueueStyle($handle . '-style', $this->assetUrl($extension['css']));
        }

        if (isset($options['data'])) {
            $this->addBookiFlexData([
                'extensions' => [
                    $extensionName => $options['data'],
                ],
            ], $context);
        }
    }

    /**
     * Add data to global BookiFlex object
     *
     * @param array $data Data to add to BookiFlex
     * @param AssetContext $context Context (admin or frontend)
     */
    public function addBookiFlexData(array $data, AssetContext $context): void
    {
        $this->bookiFlexData[$context->value] = array_merge_recursive(
            $this->bookiFlexData[$context->value] ?? [],
            $data
        );
    }

    /**
     * Register assets for extension
     *
     * @param string $extensionName Extension name from manifest.json
     * @param array $options Options:
     *   - context: AssetContext - loading context (required)
     *   - deps: array - script dependencies
     *   - data: array - data for BookiFlex.extensions.{name}
     *   - in_footer: bool - load in footer (default true)
     */
    public function enqueueExtensionAssets(string $extensionName, array $options = []): void
    {
        $manifest = $this->getManifest();

        if (!isset($manifest['prod']['extensions'][$extensionName])) {
            $this->logger?->warning('BookiFlex: Extension not found in manifest', ['extension' => $extensionName]);
            return;
        }

        $extension = $manifest['prod']['extensions'][$extensionName];
        $this->enqueueExtensionAssetsFromPath($extension, $extensionName, $options);
    }

    /**
     * Register extension assets from manifest data
     *
     * @param array $extension Extension data from manifest
     * @param string $extensionName Extension name
     * @param array $options Options
     */
    protected function enqueueExtensionAssetsFromPath(array $extension, string $extensionName, array $options = []): void
    {
        $handle = 'bookiflex-ext-' . $extensionName;
        $deps = $options['deps'] ?? ['bookiflex-vue'];
        $inFooter = $options['in_footer'] ?? true;
        $context = $options['context'] ?? AssetContext::ADMIN;

        // JS
        if (isset($extension['js'])) {
            $this->enqueueScript($handle, $this->assetUrl($extension['js']), $deps, $inFooter);
        }

        // CSS
        if (isset($extension['css'])) {
            $this->enqueueStyle($handle . '-style', $this->assetUrl($extension['css']));
        }

        // Data
        if (isset($options['data'])) {
            $this->addBookiFlexData([
                'extensions' => [
                    $extensionName => $options['data']
                ]
            ], $context);
        }
    }

    /**
     * Finalize BookiFlex for admin panel
     */
    public function finalizeAdminBookiFlexData(): void
    {
        $hasData    = !empty($this->bookiFlexData['admin']);
        $hasScripts = !empty($this->inlineScripts['admin']);

        if (!$hasData && !$hasScripts) {
            return;
        }

        if (!wp_script_is('bookiflex-vue', 'enqueued') &&
            !wp_script_is('bookiflex-vue', 'registered')) {
            return;
        }

        if ($hasData) {
            wp_localize_script('bookiflex-vue', 'BookiFlex', $this->bookiFlexData['admin']);
        }

        foreach ($this->inlineScripts['admin'] as $script) {
            wp_add_inline_script('bookiflex-vue', $script, 'after');
        }
    }

    /**
     * Finalize BookiFlex for frontend
     */
    public function finalizeFrontendBookiFlexData(): void
    {
        $hasData    = !empty($this->bookiFlexData['frontend']);
        $hasScripts = !empty($this->inlineScripts['frontend']);

        if (!$hasData && !$hasScripts) {
            return;
        }

        if (!wp_script_is('bookiflex-vue', 'enqueued') &&
            !wp_script_is('bookiflex-vue', 'registered')) {
            return;
        }

        if ($hasData) {
            wp_localize_script('bookiflex-vue', 'BookiFlex', $this->bookiFlexData['frontend']);
        }

        foreach ($this->inlineScripts['frontend'] as $script) {
            wp_add_inline_script('bookiflex-vue', $script, 'after');
        }
    }
}
