<?php

declare(strict_types=1);

namespace CliqThemes\UI\WordPress;

use CliqThemes\UI\Support\FeatureFlags;

/**
 * Minimal integration surface for bootstrapping the UI bundle inside a WordPress host.
 */
final class Integration
{
    public const DEFAULT_SCRIPT_HANDLE = 'cliqthemes-ui-app';
    public const DEFAULT_STYLE_HANDLE = 'cliqthemes-ui-styles';
    public const DEFAULT_ROOT_ID = 'cliqthemes-ui-root';
    private const STYLESHEET_FILENAME = 'style-index.css';

    private FeatureFlags $flags;

    private string $assetPath;

    private string $assetUrl;

    private string $scriptHandle;

    private string $styleHandle;

    private string $rootId;

    private string $hostId;

    /**
     * @var array<string, bool>
     */
    private array $allowedHooks = [];

    private function __construct(
        FeatureFlags $flags,
        string $assetPath,
        string $assetUrl,
        string $scriptHandle,
        string $styleHandle = self::DEFAULT_STYLE_HANDLE,
        string $rootId = self::DEFAULT_ROOT_ID,
        string $hostId = ''
    ) {
        $this->flags = $flags;
        $this->assetPath = rtrim($assetPath, '\\/');
        $this->assetUrl = rtrim($assetUrl, '/');
        $this->scriptHandle = $scriptHandle !== '' ? $scriptHandle : self::DEFAULT_SCRIPT_HANDLE;
        $this->styleHandle = $styleHandle !== '' ? $styleHandle : self::DEFAULT_STYLE_HANDLE;
        $this->rootId = $rootId !== '' ? $rootId : self::DEFAULT_ROOT_ID;
        $this->hostId = $hostId;
    }

    /**
     * Bootstraps the integration using the provided configuration and registers WordPress hooks immediately.
     *
     * @param array<string,mixed> $config
     */
    public static function boot(array $config = []): self
    {
        $flags = FeatureFlags::fromArray((array) ($config['flags'] ?? []));
        $assetPath = self::resolveAssetPath($config);
        $assetUrl = self::resolveAssetUrl($config);
        $hostId = self::resolveHostId($config);
        $prefix = $hostId !== '' ? $hostId . '-' : '';
        $scriptHandle = (string) ($config['script_handle'] ?? $prefix . self::DEFAULT_SCRIPT_HANDLE);
        $styleHandle = (string) ($config['style_handle'] ?? $prefix . self::DEFAULT_STYLE_HANDLE);
        $rootId = (string) ($config['root_id'] ?? $prefix . self::DEFAULT_ROOT_ID);

        $instance = new self($flags, $assetPath, $assetUrl, $scriptHandle, $styleHandle, $rootId, $hostId);
        $instance->register();

        // Lifecycle hook so hosts/extensions can act once the framework is booted
        // (register providers, add REST/data filters, enqueue companions, …).
        if (function_exists('do_action')) {
            do_action('cliqthemes_ui_boot', $instance);
            if ($hostId !== '') {
                do_action('cliqthemes_ui_boot_' . str_replace('-', '_', $hostId), $instance);
            }
        }

        return $instance;
    }

    /**
     * Registers WordPress hooks required to enqueue the UI bundle.
     */
    public function register(): void
    {
        if (! $this->flags->wordpressBridgeEnabled()) {
            return;
        }

        if (! function_exists('add_action')) {
            return;
        }

        add_action('admin_enqueue_scripts', [$this, 'enqueueAssets'], 10, 1);
    }

    /**
     * Registers scripts and styles if the current admin hook is allowed.
     */
    public function enqueueAssets(string $hookSuffix = ''): void
    {
        if ($this->allowedHooks !== [] && $hookSuffix !== '' && ! isset($this->allowedHooks[$hookSuffix])) {
            return;
        }

        if (! function_exists('wp_register_script')) {
            return;
        }

        $assetMeta = $this->loadAssetMeta('index');
        if ($assetMeta === null || $this->assetUrl === '') {
            return;
        }

        wp_register_script(
            $this->scriptHandle,
            $this->assetUrl . '/index.js',
            $assetMeta['dependencies'],
            $assetMeta['version'],
            true
        );

        // The framework's own React strings use the 'cliq-store-locator' text domain;
        // load their JS translations from the framework's languages directory
        // (resources/admin/build → framework root → /languages).
        if (function_exists('wp_set_script_translations')) {
            wp_set_script_translations(
                $this->scriptHandle,
                'cliq-store-locator',
                dirname($this->assetPath, 3) . '/languages'
            );
        }

        if (
            defined('CLIQTHEMES_UI_GMAP_KEY')
            && function_exists('wp_add_inline_script')
        ) {
            $apiKey = trim((string) constant('CLIQTHEMES_UI_GMAP_KEY'));

            if ($apiKey !== '') {
                $inlineScript = implode("\n", [
                    'window.CLIQTHEMES_UI = window.CLIQTHEMES_UI || {};',
                    'window.CLIQTHEMES_UI.config = window.CLIQTHEMES_UI.config || {};',
                    'window.CLIQTHEMES_UI.config.googleMaps = window.CLIQTHEMES_UI.config.googleMaps || {};',
                    'if (! window.CLIQTHEMES_UI.config.googleMaps.apiKey) {',
                    '        window.CLIQTHEMES_UI.config.googleMaps.apiKey = ' . wp_json_encode($apiKey) . ';',
                    '}',
                ]);

                wp_add_inline_script($this->scriptHandle, $inlineScript, 'before');
            }
        }

        $stylesheetPath = $this->assetPath . DIRECTORY_SEPARATOR . self::STYLESHEET_FILENAME;
        if (file_exists($stylesheetPath) && function_exists('wp_register_style')) {
            $styleDeps = ['wp-components'];
            // @wordpress/scripts only merges CSS into style-index.css when its
            // source file is literally named style.(s)css — SliderControl's
            // vendored slider CSS lands in this bundle's separate index.css
            // chunk instead, so it needs its own registration; a dependency
            // here prints it first so our --cui-slider-* overrides win the
            // cascade.
            $vendorCssPath = $this->assetPath . DIRECTORY_SEPARATOR . 'index.css';
            if (file_exists($vendorCssPath)) {
                wp_register_style($this->styleHandle . '-slider-vendor', $this->assetUrl . '/index.css', [], $assetMeta['version']);
                $styleDeps[] = $this->styleHandle . '-slider-vendor';
            }
            wp_register_style(
                $this->styleHandle,
                $this->assetUrl . '/' . self::STYLESHEET_FILENAME,
                // Depend on WordPress's component stylesheet so design-tools
                // controls (UnitControl, BoxControl, BorderControl, etc.) are
                // styled correctly rather than rendering as broken inputs.
                $styleDeps,
                $assetMeta['version']
            );
        }
    }

    /**
     * Outputs the DOM node that the React bundle will mount into.
     */
    public function renderMountPoint(): void
    {
        printf(
            '<div id="%s" class="cliqthemes-ui-root" data-cliqthemes-ui="ready"></div>',
            esc_attr($this->rootId)
        );
    }

    /**
     * Restricts automatic asset enqueuing to the provided hook suffix.
     */
    public function allowPageHook(string $hookSuffix): void
    {
        $this->allowedHooks[$hookSuffix] = true;
    }

    /**
     * Returns the handle used when registering the JavaScript bundle.
     */
    public function scriptHandle(): string
    {
        return $this->scriptHandle;
    }

    /**
     * Returns the handle used when registering the stylesheet.
     */
    public function styleHandle(): string
    {
        return $this->styleHandle;
    }

    /**
     * Registers and enqueues an optional Cliq UI module from
     * `resources/{module}/build`. Optional modules depend on UI Core and are
     * evaluated before host extension bundles that list the returned handle.
     *
     * Free products simply never package or call this module.
     */
    public function enqueueModule(string $module, string $handle = ''): ?string
    {
        $module = sanitize_key($module);
        if ($module === '' || ! function_exists('wp_enqueue_script')) {
            return null;
        }

        $resourcesPath = dirname($this->assetPath, 2);
        $modulePath = $resourcesPath . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . 'build';
        $assetFile = $modulePath . DIRECTORY_SEPARATOR . 'index.asset.php';
        if (! file_exists($assetFile)) {
            return null;
        }

        $assetMeta = include $assetFile;
        if (! is_array($assetMeta)) {
            return null;
        }

        $moduleHandle = $handle !== ''
            ? $handle
            : ($this->hostId !== '' ? $this->hostId . '-' : '') . 'cliqthemes-ui-' . $module;
        $dependencies = isset($assetMeta['dependencies']) && is_array($assetMeta['dependencies'])
            ? array_values($assetMeta['dependencies'])
            : [];
        $dependencies[] = $this->scriptHandle;
        $dependencies = array_values(array_unique($dependencies));
        $version = isset($assetMeta['version']) ? (string) $assetMeta['version'] : '1.0.0';

        $resourcesUrl = preg_replace('~/admin/build$~', '', $this->assetUrl);
        if (! is_string($resourcesUrl) || $resourcesUrl === $this->assetUrl) {
            return null;
        }
        $moduleUrl = rtrim($resourcesUrl, '/') . '/' . $module . '/build';

        wp_enqueue_script(
            $moduleHandle,
            $moduleUrl . '/index.js',
            $dependencies,
            $version,
            true
        );

        if (function_exists('wp_set_script_translations')) {
            wp_set_script_translations(
                $moduleHandle,
                'cliq-store-locator',
                dirname($this->assetPath, 3) . '/languages'
            );
        }

        $styleDependencies = [$this->styleHandle];
        $vendorStylePath = $modulePath . DIRECTORY_SEPARATOR . 'index.css';
        if (file_exists($vendorStylePath) && function_exists('wp_enqueue_style')) {
            $vendorStyleHandle = $moduleHandle . '-vendor';
            wp_enqueue_style(
                $vendorStyleHandle,
                $moduleUrl . '/index.css',
                [],
                $version
            );
            $styleDependencies[] = $vendorStyleHandle;
        }

        $stylePath = $modulePath . DIRECTORY_SEPARATOR . 'style-index.css';
        if (file_exists($stylePath) && function_exists('wp_enqueue_style')) {
            $styleHandle = $moduleHandle . '-styles';
            wp_enqueue_style(
                $styleHandle,
                $moduleUrl . '/style-index.css',
                $styleDependencies,
                $version
            );
        }

        return $moduleHandle;
    }

    /**
     * Returns the DOM id the React bundle mounts into (overridable via config
     * `root_id` so two cliq-ui-based plugins can coexist on one page).
     */
    public function rootId(): string
    {
        return $this->rootId;
    }

    /**
     * Resolve a stable namespace for this host plugin. Explicit `host_id` is
     * preferred; `plugin_file` is a safe convenience fallback. Legacy callers
     * that provide neither retain the historical unscoped defaults.
     *
     * @param array<string,mixed> $config
     */
    private static function resolveHostId(array $config): string
    {
        $candidate = trim((string) ($config['host_id'] ?? ''));
        if ($candidate === '' && is_string($config['plugin_file'] ?? null)) {
            $candidate = basename(dirname((string) $config['plugin_file']));
        }

        if (function_exists('sanitize_key')) {
            return sanitize_key($candidate);
        }

        return strtolower((string) preg_replace('/[^a-z0-9_-]+/i', '-', $candidate));
    }

    /**
     * @param array<string,mixed> $config
     */
    private static function resolveAssetPath(array $config): string
    {
        if (! empty($config['asset_path'])) {
            return (string) $config['asset_path'];
        }

        return implode(
            DIRECTORY_SEPARATOR,
            [
                dirname(__DIR__, 2),
                'resources',
                'admin',
                'build',
            ]
        );
    }

    /**
     * @param array<string,mixed> $config
     */
    private static function resolveAssetUrl(array $config): string
    {
        if (! empty($config['asset_url'])) {
            return (string) $config['asset_url'];
        }

        $pluginFile = $config['plugin_file'] ?? null;
        if (function_exists('plugins_url') && is_string($pluginFile) && $pluginFile !== '') {
            $relativePath = 'vendor/cliqthemes/ui/resources/admin/build';

            return rtrim(
                plugins_url($relativePath, $pluginFile),
                '/'
            );
        }

        return '';
    }

    /**
     * @return array{dependencies:array<int,string>,version:string}|null
     */
    private function loadAssetMeta(string $entry): ?array
    {
        $assetFile = $this->assetPath . DIRECTORY_SEPARATOR . $entry . '.asset.php';
        if (! file_exists($assetFile)) {
            return null;
        }

        $assetMeta = include $assetFile;
        if (! is_array($assetMeta)) {
            return null;
        }

        $dependencies = isset($assetMeta['dependencies']) && is_array($assetMeta['dependencies'])
            ? array_values($assetMeta['dependencies'])
            : [];
        $version = isset($assetMeta['version']) ? (string) $assetMeta['version'] : '1.0.0';

        return [
            'dependencies' => $dependencies,
            'version' => $version,
        ];
    }
}
