<?php

declare(strict_types=1);

namespace FleekDash\Admin;

use FleekDash\Admin\Iframe\EmbeddedShellAdminBarPolicy;
use FleekDash\Admin\Iframe\IframeCSSManager;
use FleekDash\Admin\Iframe\IframeHelpers;
use FleekDash\Admin\Iframe\IframeRequestContext;
use FleekDash\Admin\Iframe\IframeScriptManager;

/**
 * Class Iframe
 *
 * Manages the iframe functionality in the admin.
 *
 * @package FleekDash\Admin
 */
class Iframe
{
    /**
     * Whether the current embed request should keep the native WP admin bar inside a real iframe.
     *
     * @see EmbeddedShellAdminBarPolicy
     */
    public static function isNativeAdminBarVisibleInEmbed(): bool
    {
        return EmbeddedShellAdminBarPolicy::isNativeAdminBarVisibleInEmbed();
    }

    /**
     * Constructor: Initializes the hooks.
     */
    public function __construct()
    {
        $this->bootstrap();
    }

    /**
     * Initializes the hooks related to the iframe
     */
    public function bootstrap()
    {
        \add_action('customize_controls_enqueue_scripts', [$this, 'enqueueCustomizerCss']);
        \add_action('plugins_loaded', [$this, 'handleIframeLoad'], 20);
    }

    /**
     * Enqueue Customizer CSS
     */
    public function enqueueCustomizerCss()
    {
        IframeCSSManager::enqueueCustomizerCSS();
    }

    /**
     * Handle iframe specific modifications
     */
    public function handleIframeLoad()
    {
        EmbeddedShellAdminBarPolicy::resetRequestState();

        IframeRequestContext::redirectIfProtectedMustNotApplyToCurrentAdminUrl();

        $isIframeContext = IframeRequestContext::detectIframeContext();
        IframeRequestContext::detectProtectedMode();
        IframeHelpers::stripProtectedSuperglobalWhenPolicyOmitsProtectedParam();
        $isProtectedMode = IframeHelpers::isProtectedRequest();

        if ($isIframeContext) {
            $this->registerIframeContextHooks();
            \add_filter('wp_redirect', [$this, 'preserveIframeParameterOnRedirect'], 10, 2);
        }

        if ($isProtectedMode) {
            \add_filter('wp_redirect', [$this, 'preserveProtectedParameterOnRedirect'], 10, 2);
        }
    }

    /**
     * Register admin_enqueue_scripts and admin_head hooks for iframe context.
     */
    private function registerIframeContextHooks(): void
    {
        \add_action('current_screen', [EmbeddedShellAdminBarPolicy::class, 'apply'], 0);
        \add_filter('admin_body_class', [$this, 'appendIframeEmbedBodyClass'], 1);
        \add_action('admin_enqueue_scripts', [$this, 'enqueueEssentialIframeStyles'], 1);
        \add_action('admin_enqueue_scripts', [IframeScriptManager::class, 'registerEarlyIframeScripts'], 0);
        \add_action('admin_enqueue_scripts', [$this, 'enqueueDesignSystemTokens'], 998);
        \add_action('admin_enqueue_scripts', [$this, 'enqueueAdminBarStyles'], 999);

        // Theme sync must run on POST too (e.g. theme-install.php after upload).
        // Without it, html lacks data-theme + light/dark and CSS vars like --foreground stay undefined.
        \add_action('admin_enqueue_scripts', [$this, 'addUrlPreservationScript'], 1);
        \add_action('admin_enqueue_scripts', [$this, 'enqueueIframeAdminAjaxCompat'], 9999);
        \add_action('admin_head', [$this, 'addBlockingThemeScript'], -9999);
        \add_action('admin_head', [$this, 'echoCriticalWpwrapSurfaceStyle'], -9998);
        \add_action('admin_head', [$this, 'printWpwrapSurfacePinScript'], -9997);
        \add_action('admin_head', [$this, 'printWpwrapSurfacePinInit'], -9996);
        \add_action('in_admin_header', [IframeScriptManager::class, 'addEarlyInsideIframeBodyClassScript'], 0);
        \add_action('admin_head', [$this, 'addIframeScripts']);
    }

    /**
     * Embed-mode body class on first paint (fleekdash-frame=1) so iframe CSS applies before JS runs.
     *
     * inside-iframe is applied on in_admin_header (first paint) and re-synced by footer JS.
     *
     * @param string $classes Space-separated admin body classes.
     * @return string
     */
    public function appendIframeEmbedBodyClass(string $classes): string
    {
        if (!IframeRequestContext::detectIframeContext()) {
            return $classes;
        }

        if (strpos($classes, 'fleekdash-iframe') === false) {
            $classes .= ' fleekdash-iframe';
        }

        return $classes;
    }

    /**
     * Enqueue essential iframe styles (always loaded, even in protected mode)
     */
    public function enqueueEssentialIframeStyles(): void
    {
        IframeCSSManager::enqueueEssentialStyles();
    }

    /**
     * Enqueue iframe design tokens (iframe-tokens.css) and optional admin skin (index.css).
     */
    public function enqueueDesignSystemTokens(): void
    {
        IframeCSSManager::enqueueDesignSystemTokens();
    }

    /**
     * Enqueue admin bar styles (when adminbar is visible - default behavior)
     */
    public function enqueueAdminBarStyles(): void
    {
        IframeCSSManager::enqueueAdminBarStyles();
    }

    /**
     * Add URL preservation script (prevents Gutenberg from stripping FleekDash URL params)
     *
     * This runs with priority 1 to execute before Gutenberg loads.
     * Intercepts history.replaceState to preserve fleekdash-frame and fleek-compat params.
     */
    public function addUrlPreservationScript(): void
    {
        IframeScriptManager::addUrlPreservationScript();
    }

    /**
     * Keep admin-ajax URL clean inside FleekDash iframe.
     *
     * Some scripts localize a global named {@code ajaxurl} as an object ({@code { url: string }}), which
     * breaks core {@code $.post(ajaxurl, ...)} and Heartbeat. Other code can also leave
     * {@code admin-ajax.php?fleekdash-frame=1} with no {@code action}, producing 400 on GET.
     * Runs after jQuery (fixes {@code window.ajaxurl} before Heartbeat) and after wp-util (fixes {@code wp.ajax.settings.url} before media).
     */
    public function enqueueIframeAdminAjaxCompat(): void
    {
        if (!isset($_GET['fleekdash-frame']) || $_GET['fleekdash-frame'] !== '1' || !\is_admin()) {
            return;
        }

        $clean = \admin_url('admin-ajax.php');
        $json  = \wp_json_encode($clean, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES);
        if ($json === false) {
            return;
        }

        $frameNeedle = 'fleekdash-frame';
        $actionNeedle = 'action=';

        $fixAjaxurl = '(function(){try{var c=' . $json . ';'
            . 'if(typeof window.ajaxurl==="object"&&window.ajaxurl&&typeof window.ajaxurl.url==="string"){'
            . 'window.ajaxurl=window.ajaxurl.url.split("?")[0];}'
            . 'if(typeof window.ajaxurl==="string"&&window.ajaxurl.indexOf("' . $frameNeedle . '")!==-1'
            . '&&window.ajaxurl.indexOf("' . $actionNeedle . '")===-1){window.ajaxurl=c;}'
            . '}catch(e){}})();';

        $fixWpAjax = '(function(){try{var c=' . $json . ';'
            . 'if(window.wp&&wp.ajax&&wp.ajax.settings&&typeof wp.ajax.settings.url==="string"){'
            . 'var u=wp.ajax.settings.url;'
            . 'if(u.indexOf("' . $frameNeedle . '")!==-1&&u.indexOf("' . $actionNeedle . '")===-1){wp.ajax.settings.url=c;}}'
            . '}catch(e){}})();';

        if (\wp_script_is('jquery', 'queue')) {
            \wp_add_inline_script('jquery', $fixAjaxurl, 'after');
        }

        if (\wp_script_is('wp-util', 'queue')) {
            \wp_add_inline_script('wp-util', $fixWpAjax, 'after');
        }
    }

    /**
     * First-paint #wpwrap surface CSS (server-rendered, uses body.wp-admin before JS classes).
     *
     * @return void
     */
    public function echoCriticalWpwrapSurfaceStyle(): void
    {
        $css = IframeCSSManager::getCriticalWpwrapSurfaceCssForFirstPaint();
        echo '<style id="fleekdash-critical-wpwrap-surface-php">' . $css . '</style>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static CSS from manager
    }

    /**
     * Load shared #wpwrap surface pin module (after blocking palette script).
     *
     * @return void
     */
    public function printWpwrapSurfacePinScript(): void
    {
        IframeScriptManager::printWpwrapSurfacePinScript();
    }

    /**
     * Start #wpwrap pin observers after module + palette are ready.
     *
     * @return void
     */
    public function printWpwrapSurfacePinInit(): void
    {
        IframeScriptManager::printWpwrapSurfacePinInit();
    }

    /**
     * Add BLOCKING theme script (prevents flash by blocking rendering)
     *
     * This runs with priority -9999 to execute before ANY other scripts.
     * Uses blocking technique to prevent HTML rendering until styles are applied.
     */
    public function addBlockingThemeScript(): void
    {
        EmbeddedShellAdminBarPolicy::ensureApplied();
        IframeScriptManager::addBlockingThemeScript();
    }

    /**
     * Add critical theme script FIRST (prevents flash)
     *
     * This runs with priority -999 to execute before any other scripts,
     * ensuring styles are applied before HTML renders.
     */
    public function addCriticalThemeScript(): void
    {
        IframeScriptManager::addCriticalThemeScript();
    }

    /**
     * Add simple scripts to notify the parent about loading
     */
    public function addIframeScripts(): void
    {
        IframeScriptManager::addIframeScripts();
    }

    /**
     * Preserve the 'fleekdash-frame' parameter on redirects originating from the iframe.
     *
     * @param string $location The redirect location URL.
     * @param int    $status   The redirect status code.
     * @return string The potentially modified redirect location URL.
     */
    public function preserveIframeParameterOnRedirect($location, $status): string
    {
        return IframeHelpers::preserveIframeParameter($location, $status);
    }

    /**
     * Preserve the 'protected' parameter on redirects when in protected mode.
     *
     * @param string $location The redirect location URL.
     * @param int    $status   The redirect status code.
     * @return string The potentially modified redirect location URL.
     */
    public function preserveProtectedParameterOnRedirect($location, $status): string
    {
        return IframeHelpers::preserveProtectedParameter($location, $status);
    }

}
