<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Settings;

use Ekesto\CacheInvalidator\Core\FeatureFlags;
use Ekesto\CacheInvalidator\Integrations\Caching\AdapterManager;
use Ekesto\CacheInvalidator\InvalidatorRuntime;
use Ekesto\CacheInvalidator\Settings;
use Ekesto\CacheAutopilot\Support\SupportLog;
use Ekesto\CacheAutopilot\PluginMetadata;
use Ekesto\CacheAutopilot\AccessControl;
use Ekesto\CacheAutopilot\ProEntitlement;

/**
 * Admin page registration, rendering, and asset bootstrapping.
 */
final class SettingsAdminPage
{
    private const DISMISS_UNCONFIGURED_NOTICE_ACTION = 'ekesto_ci_dismiss_unconfigured_post_types_notice';
    private const DISABLED_INVALIDATION_NOTICE_ID = 'ekesto-eci-disabled-invalidation-notice';
    private const DISABLED_INVALIDATION_TOGGLE_EVENT = 'ekesto-cache-invalidator:invalidation-state';
    private const MISSING_CACHE_ADAPTER_NOTICE_ID = 'ekesto-eci-missing-cache-adapter-notice';
    private const SUPPORTED_CACHE_ADAPTERS_DOCS_URL = 'https://wpcacheautopilot.com/docs/supported-integrations/#cache-adapters';

    private SettingsPayloadBuilder $payloadBuilder;
    private bool $assetsMissingNoticeShown = false;
    private ?string $pageHook = null;

    /**
     * @param SettingsPayloadBuilder $payloadBuilder Settings payload builder for localized boot data.
     */
    public function __construct(SettingsPayloadBuilder $payloadBuilder)
    {
        $this->payloadBuilder = $payloadBuilder;
    }

    /**
     * Registers the settings page.
     *
     * Admins get the traditional Settings submenu. Non-admins with access
     * get a top-level menu item with the plugin logo as icon.
     *
     * @return void
     */
    public function registerSettingsPage(): void
    {
        if (self::currentUserIsAdmin()) {
            $hook = add_options_page(
                __('Cache Invalidator', 'cache-autopilot'),
                __('Cache Invalidator', 'cache-autopilot'),
                'read',
                Settings::SETTINGS_PAGE_SLUG,
                [$this, 'renderPage']
            );
            $this->pageHook = is_string($hook) && $hook !== '' ? $hook : null;
            return;
        }

        if (!AccessControl::userCanManage()) {
            return;
        }

        $hook = add_menu_page(
            __('Cache Invalidator', 'cache-autopilot'),
            __('Cache Invalidator', 'cache-autopilot'),
            'read',
            Settings::SETTINGS_PAGE_SLUG,
            [$this, 'renderPage'],
            'none'
        );
        $this->pageHook = is_string($hook) && $hook !== '' ? $hook : null;
    }

    /**
     * Append a Cache Invalidator settings link to the plugin's action links on the Plugins page.
     *
     * @param array $links Existing action links.
     * @return array Modified links.
     */
    public function addPluginSettingsLink(array $links): array
    {
        $url = InvalidatorRuntime::settingsPageUrl();
        $link = sprintf('<a href="%1$s">%2$s</a>', esc_url($url), __('Invalidator Settings', 'cache-autopilot'));
        array_push($links, $link);

        return $links;
    }

    /**
     * Append a Lite-only PRO upgrade link to the plugin's action links on the Plugins page.
     *
     * @param array $links Existing action links.
     * @return array Modified links.
     */
    public function addPluginUpgradeLink(array $links): array
    {
        if (FeatureFlags::isPremium()) {
            return $links;
        }

        $upgradeLink = sprintf(
            '<br><a href="%1$s" style="font-weight: bold;" target="_blank" rel="noopener noreferrer">%2$s</a>',
            esc_url('https://wpcacheautopilot.com/'),
            __('Get Cache Autopilot PRO', 'cache-autopilot')
        );
        array_push($links, $upgradeLink);

        return $links;
    }

    /**
     * Render settings page (React container only).
     *
     * @return void
     */
    public function renderPage(): void
    {
        if (!AccessControl::userCanManage()) {
            return;
        }

        printf(
            '<div id="%1$s"></div>',
            esc_attr(Settings::ROOT_ELEMENT_ID)
        );
    }

    private static function currentUserIsAdmin(): bool
    {
        return (function_exists('is_super_admin') && is_super_admin())
            || (function_exists('current_user_can') && current_user_can('manage_options'));
    }

    /**
     * Enqueue React app assets for settings page.
     *
     * @param string $hook Current admin page hook.
     * @return void
     */
    public function enqueueAdminAssets(string $hook): void
    {
        if ($this->pageHook === null || $hook !== $this->pageHook) {
            return;
        }

        // Load React app with auto-generated dependencies.
        $scriptAssetPath = InvalidatorRuntime::path() . 'build/js/settings.asset.php';
        $styleAssetPath = InvalidatorRuntime::path() . 'build/css/settings.asset.php';
        $scriptPath = InvalidatorRuntime::path() . 'build/js/settings.js';
        $stylePath = InvalidatorRuntime::path() . 'build/css/settings.css';

        if (!is_file($scriptAssetPath) || !is_file($scriptPath) || !is_file($stylePath)) {
            $this->assetsMissingNoticeShown = true;
            return;
        }

        $scriptAsset = $this->loadAssetMetadata($scriptAssetPath);
        $styleAsset = $this->loadAssetMetadata($styleAssetPath);

        wp_enqueue_script(
            Settings::SCRIPT_HANDLE,
            InvalidatorRuntime::url() . 'build/js/settings.js',
            $scriptAsset['dependencies'],
            $this->resolveAssetVersion($scriptAsset, $scriptPath),
            ['in_footer' => true]
        );
        wp_set_script_translations(
            Settings::SCRIPT_HANDLE,
            'cache-autopilot',
            dirname((string) EKESTO_CA_PLUGIN_FILE) . '/languages'
        );

        wp_enqueue_style(
            Settings::STYLE_HANDLE,
            InvalidatorRuntime::url() . 'build/css/settings.css',
            array_values(
                array_unique(
                    array_merge(['wp-components'], $styleAsset['dependencies'])
                )
            ),
            $this->resolveAssetVersion($styleAsset, $stylePath)
        );

        // Pass config + initial settings data to React app.
        // Uses wp_add_inline_script instead of wp_localize_script to preserve
        // boolean and integer types (wp_localize_script coerces everything to strings).
        $bootData = [
            'config' => [
                'restPath' => '/' . Settings::REST_NAMESPACE . Settings::REST_SETTINGS_ROUTE,
                'restNamespace' => Settings::REST_NAMESPACE,
                'restRoot' => esc_url_raw(rest_url()),
                'restNonce' => wp_create_nonce('wp_rest'),
                'rootId' => Settings::ROOT_ELEMENT_ID,
                'pageSlug' => Settings::SETTINGS_PAGE_SLUG,
                'screenHook' => $this->pageHook ?? Settings::SETTINGS_PAGE_HOOK,
                'isAdmin' => self::currentUserIsAdmin(),
                'canManageAccessControl' => AccessControl::userCanManage(),
                'features' => FeatureFlags::all(),
                'scriptHandle' => Settings::SCRIPT_HANDLE,
                'styleHandle' => Settings::STYLE_HANDLE,
                'pluginVersion' => PluginMetadata::VERSION,
                'isPremium' => ProEntitlement::canUseProFeatures(),
            ],
            'initial' => $this->payloadBuilder->buildSettingsPayload(),
            'supportDebug' => SupportLog::getStatusForUi(),
        ];
        wp_add_inline_script(
            Settings::SCRIPT_HANDLE,
            'var ' . Settings::JS_BOOT_OBJECT . ' = ' . wp_json_encode($bootData) . ';',
            'before'
        );

        wp_add_inline_script(
            Settings::SCRIPT_HANDLE,
            self::disabledInvalidationNoticeInlineScript(),
            'after'
        );
    }

    /**
     * Load and normalize build metadata from an asset file.
     *
     * @param string $assetPath Asset file path.
     * @return array{dependencies: string[], version: string}
     */
    private function loadAssetMetadata(string $assetPath): array
    {
        if (!is_file($assetPath)) {
            return [
                'dependencies' => [],
                'version' => '',
            ];
        }

        $asset = include $assetPath;
        if (!is_array($asset)) {
            return [
                'dependencies' => [],
                'version' => '',
            ];
        }

        $dependencies = [];
        if (isset($asset['dependencies']) && is_array($asset['dependencies'])) {
            $dependencies = array_values(
                array_filter(
                    $asset['dependencies'],
                    static fn($dependency): bool => is_string($dependency) && $dependency !== ''
                )
            );
        }

        $version = '';
        if (isset($asset['version']) && is_scalar($asset['version'])) {
            $version = trim((string) $asset['version']);
        }

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

    /**
     * Resolve a stable asset version string for cache busting.
     *
     * @param array{dependencies: string[], version: string} $asset Build metadata.
     * @param string $filePath Asset file path.
     * @return string
     */
    private function resolveAssetVersion(array $asset, string $filePath): string
    {
        if ($asset['version'] !== '') {
            return $asset['version'];
        }

        if (function_exists('hash_file') && is_file($filePath) && is_readable($filePath)) {
            $hash = hash_file('sha256', $filePath);
            if (is_string($hash) && $hash !== '') {
                return $hash;
            }
        }

        return PluginMetadata::VERSION;
    }

    /**
     * Render global admin notice when invalidation is disabled.
     *
     * @return void
     */
    public function renderGlobalAdminNotice(): void
    {
        if (!AccessControl::userCanManage()) {
            return;
        }

        if (!function_exists('ekesto_ca_admin_notice_screen_is_allowed') || !\ekesto_ca_admin_notice_screen_is_allowed()) {
            return;
        }

        $this->renderMissingCacheAdapterAdminNotice();

        if ($this->assetsMissingNoticeShown) {
            printf(
                '<div class="notice notice-error"><p>%1$s</p></div>',
                esc_html__('Cache Invalidator settings assets are missing from the plugin package. Reinstall or redeploy the plugin build artifacts.', 'cache-autopilot')
            );
        }

        $enabled = InvalidatorRuntime::settingsRepository()->isInvalidationEnabled();
        if (!$enabled) {
            $settingsUrl = InvalidatorRuntime::settingsPageUrl(['tab' => 'settings']);

            printf(
                '<div id="%6$s" class="notice notice-warning is-dismissible ekesto-cache-autopilot-notice"><p><strong>%1$s</strong> %2$s</p>' .
                '<p>%3$s</p>' .
                '<div class="ekesto-cache-autopilot-notice__actions"><a href="%4$s" class="button button-secondary">%5$s</a></div>' .
                '</div>',
                esc_html__('Cache Invalidator:', 'cache-autopilot'),
                esc_html__('The main switch is OFF.', 'cache-autopilot'),
                esc_html__('All Cache Autopilot invalidation is disabled until re-enabled in settings.', 'cache-autopilot'),
                esc_url($settingsUrl),
                esc_html__('Go to settings', 'cache-autopilot'),
                esc_attr(self::DISABLED_INVALIDATION_NOTICE_ID)
            );
        }

        $this->renderUnconfiguredPostTypesAdminNotice();
    }

    /**
     * Return footer JS for hiding the disabled-invalidation notice immediately after React saves.
     *
     * @return string
     */
    private static function disabledInvalidationNoticeInlineScript(): string
    {
        return sprintf(
            '(function(){var notice=document.getElementById(%1$s);if(!notice){return;}window.addEventListener(%2$s,function(event){var enabled=!!(event&&event.detail&&event.detail.enabled===true);notice.style.display=enabled?"none":"";notice.classList.toggle("is-hidden",enabled);});})();',
            wp_json_encode(self::DISABLED_INVALIDATION_NOTICE_ID),
            wp_json_encode(self::DISABLED_INVALIDATION_TOGGLE_EVENT)
        );
    }

    /**
     * Render a persistent admin warning while no supported cache adapter is active.
     *
     * @return void
     */
    private function renderMissingCacheAdapterAdminNotice(): void
    {
        if (AdapterManager::hasAvailableAdapters()) {
            return;
        }

        printf(
            '<div id="%1$s" class="notice notice-warning %2$s"><p><strong>%3$s</strong> %4$s</p><p><a href="%5$s" class="button-link" target="_blank" rel="noopener noreferrer">%6$s</a></p></div>',
            esc_attr(self::MISSING_CACHE_ADAPTER_NOTICE_ID),
            esc_attr('ekesto-eci-missing-cache-adapter-notice'),
            esc_html__('Cache Invalidator:', 'cache-autopilot'),
            esc_html__('No supported caching plugin detected. Cache purges will not run until a compatible cache plugin is installed and activated.', 'cache-autopilot'),
            esc_url(self::SUPPORTED_CACHE_ADAPTERS_DOCS_URL),
            esc_html__('Supported cache plugins', 'cache-autopilot') . ' ↗'
        );
    }

    /**
     * Handle oldschool admin notice dismissal action.
     *
     * @return void
     */
    public function handleDismissUnconfiguredPostTypesNoticeAction(): void
    {
        if (!AccessControl::userCanManage()) {
            wp_die(esc_html__('You do not have permission to access this page.', 'cache-autopilot'));
        }

        check_admin_referer(self::DISMISS_UNCONFIGURED_NOTICE_ACTION);
        $this->payloadBuilder->dismissUnconfiguredPostTypesNotice();

        $redirect = wp_get_referer();
        if (!is_string($redirect) || $redirect === '') {
            $redirect = admin_url();
        }

        wp_safe_redirect($redirect);
        exit;
    }

    /**
     * Render oldschool admin notice for unconfigured eligible post types.
     *
     * @return void
     */
    private function renderUnconfiguredPostTypesAdminNotice(): void
    {
        if (!$this->payloadBuilder->shouldShowUnconfiguredPostTypesNotice()) {
            return;
        }

        $unconfiguredPostTypes = $this->payloadBuilder->getUnconfiguredEligiblePostTypes();
        if (empty($unconfiguredPostTypes)) {
            return;
        }

        $labels = array_values(array_map('strval', $unconfiguredPostTypes));
        $settingsUrl = InvalidatorRuntime::settingsPageUrl(['tab' => 'settings']);
        $dismissUrl = wp_nonce_url(
            admin_url('admin-post.php?action=' . self::DISMISS_UNCONFIGURED_NOTICE_ACTION),
            self::DISMISS_UNCONFIGURED_NOTICE_ACTION
        );

        printf(
            '<div class="notice notice-info"><p><strong>%1$s</strong>: %2$s</p><p><strong>%3$s</strong></p><p>%4$s</p><p><a href="%5$s" class="button button-secondary">%6$s</a> <a href="%7$s" class="button-link">%8$s</a></p></div>',
            esc_html__('Cache Invalidator', 'cache-autopilot'),
            esc_html__('New post types available:', 'cache-autopilot'),
            esc_html(implode(', ', $labels)),
            esc_html__('Review them in Post Type Tabs.', 'cache-autopilot'),
            esc_url($settingsUrl),
            esc_html__('Review in Post Type Tabs', 'cache-autopilot'),
            esc_url($dismissUrl),
            esc_html__('Dismiss', 'cache-autopilot')
        );
    }

}
