<?php
if (!defined('ABSPATH')) exit;

class SailWP_Copilot_Plugin {
    private static $instance = null;

    public static function instance() {
        if (self::$instance === null) self::$instance = new self();
        return self::$instance;
    }

    public function init() {
        // Fatal shield first so it is armed before any other handlers
        // can register and trigger a fatal during boot.
        new SailWP_Copilot_Fatal_Shield();
        new SailWP_Copilot_Settings();
        new SailWP_Copilot_REST();
        new SailWP_Copilot_Tools();
        new SailWP_Copilot_Rail();
        // Activation notice is plugin-only (the theme bundle does not ship
        // it). Guard so the same class-plugin.php works in both contexts.
        if (class_exists('SailWP_Copilot_Activation_Notice')) {
            new SailWP_Copilot_Activation_Notice();
        }
        // Self-hosted update checker is plugin-only — the theme bundle is
        // shipped inside the start24 theme and updates with that.
        if (class_exists('SailWP_Copilot_Update_Checker') && defined('SAILWP_COPILOT_FILE')) {
            new SailWP_Copilot_Update_Checker(SAILWP_COPILOT_FILE);
        }
        new SailWP_Copilot_Fullpage_Renderer();
        // SEO meta output for designed pages — only registers itself
        // when no competing SEO plugin (Yoast / Rank Math / AIOSEO /
        // SEOPress / The SEO Framework / Slim SEO) is active.
        if (class_exists('SailWP_Copilot_SEO')) {
            new SailWP_Copilot_SEO();
        }
        // Edit-Redirect re-enabled v0.24.2 (Joost 2026-05-17): row-action
        // "Edit" on designed pages now opens the front-end with the
        // inline-edit overlay (?swcp_edit=1), "View" stays clean. Edit
        // mode is the natural place for editors; Gutenberg stays one
        // click away via the new "Code editor" row action.
        if (class_exists('SailWP_Copilot_Edit_Redirect')) {
            new SailWP_Copilot_Edit_Redirect();
        }
        if (class_exists('SailWP_Copilot_Dashboard_Hero')) {
            new SailWP_Copilot_Dashboard_Hero();
        }
        if (class_exists('SailWP_Copilot_Dashboard_Stats')) {
            new SailWP_Copilot_Dashboard_Stats();
        }
        if (class_exists('SailWP_Copilot_Versions')) {
            new SailWP_Copilot_Versions();
        }
        if (class_exists('SailWP_Copilot_Brand_Profile')) {
            (new SailWP_Copilot_Brand_Profile())->init();
        }
        if (class_exists('SailWP_Copilot_Patterns')) {
            new SailWP_Copilot_Patterns();
        }
        if (class_exists('SailWP_Copilot_Iframe_Bridge')) {
            new SailWP_Copilot_Iframe_Bridge();
        }
    }

    public static function get_license_key() {
        return trim((string) get_option('sailwp_copilot_license_key', ''));
    }

    public static function get_workspace_id() {
        return (int) get_option('sailwp_copilot_workspace_id', 0);
    }

    public static function get_site_id() {
        return (int) get_option('sailwp_copilot_site_id', 0);
    }

    public static function get_site_api_key() {
        return trim((string) get_option('sailwp_copilot_site_api_key', ''));
    }

    public static function get_ai_proxy_url() {
        $url = trim((string) get_option('sailwp_copilot_ai_proxy_url', SAILWP_COPILOT_AI_PROXY_DEFAULT));
        return rtrim($url, '/');
    }

    public static function get_cloud_url() {
        $url = trim((string) get_option('sailwp_copilot_cloud_url', SAILWP_COPILOT_CLOUD_DEFAULT));
        return rtrim($url, '/');
    }

    public static function is_connected_to_cloud() {
        return self::get_license_key() !== '' && self::get_site_api_key() !== '';
    }

    /**
     * Cache the heavy-action quota fields from a /api/v1/free-license or
     * /copilot/chat-with-tools response. Stored in a short-lived transient
     * so the rail and dashboard hero can render the counter without hitting
     * the proxy on every page load.
     */
    public static function cache_quota_from_response($body) {
        if (!is_array($body)) return;
        $quota = [
            'plan'              => isset($body['plan']) ? (string) $body['plan'] : 'free',
            'subscription_type' => isset($body['subscription_type']) ? (string) $body['subscription_type'] : '',
            'heavy_used'        => isset($body['heavy_used'])  ? (int) $body['heavy_used']  : (isset($body['opus_used'])  ? (int) $body['opus_used']  : 0),
            'heavy_limit'       => array_key_exists('heavy_limit', $body) ? (is_null($body['heavy_limit']) ? null : (int) $body['heavy_limit']) : (isset($body['opus_limit']) ? (int) $body['opus_limit'] : null),
            'period_end'        => isset($body['period_end']) ? (string) $body['period_end'] : null,
        ];
        set_transient('swcp_quota_cache', $quota, 10 * MINUTE_IN_SECONDS);
    }

    /**
     * Return the cached quota array, priming it if missing. Calls is_pro()
     * which has its own 60s transient — so this is effectively cached at
     * the request layer too. Returns null only when there's no license
     * yet (very first plugin load before the heartbeat lands).
     */
    public static function get_quota_cache() {
        $cached = get_transient('swcp_quota_cache');
        if (is_array($cached)) return $cached;
        // No cache and we have a license — prime it. is_pro() hits
        // /api/v1/free-license which refreshes the quota cache as a side
        // effect via cache_quota_from_response().
        if (self::get_license_key()) {
            self::is_pro();
            $cached = get_transient('swcp_quota_cache');
            if (is_array($cached)) return $cached;
        }
        return null;
    }

    /**
     * Check whether the current license is on a paid plan. Cached for the
     * duration of the request via a static, refreshed transient daily.
     */
    public static function is_pro() {
        $license = self::get_license_key();
        if (!$license) return false;
        // Allow ?swcp_refresh=1 to bypass cache for fresh post-upgrade checks
        $bypass = isset($_GET['swcp_refresh']);
        $key = 'swcp_plan_' . md5($license);
        if (!$bypass) {
            $cached = get_transient($key);
            if ($cached !== false) return $cached === 'pro';
        }
        $proxy = self::get_ai_proxy_url();
        $resp = wp_remote_post($proxy . '/api/v1/free-license', [
            'timeout' => 8,
            'headers' => ['Content-Type' => 'application/json'],
            'body'    => wp_json_encode(['site_url' => home_url()]),
        ]);
        $plan = 'free';
        if (!is_wp_error($resp)) {
            $body = json_decode(wp_remote_retrieve_body($resp), true);
            if (is_array($body) && !empty($body['plan']) && $body['plan'] !== 'free') {
                $plan = 'pro';
            }
            // Free piggyback: the same response carries the heavy-action
            // quota fields. Cache them so the rail can render the counter
            // without an extra round-trip.
            self::cache_quota_from_response(is_array($body) ? $body : []);
        }
        set_transient($key, $plan, MINUTE_IN_SECONDS); // short cache so post-upgrade state shows up fast
        return $plan === 'pro';
    }
}
