<?php

/**
 * Spellmatic_Core
 * Main plugin class — singleton. Registers all hooks and boots sub-systems.
 */
defined( 'ABSPATH' ) || exit;
class Spellmatic_Core {
    /** @var Spellmatic_Core|null */
    private static ?Spellmatic_Core $instance = null;

    // ── Singleton ─────────────────────────────────────────────────────────────
    public static function get_instance() : Spellmatic_Core {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->init_hooks();
    }

    // ── Hooks ─────────────────────────────────────────────────────────────────
    private function init_hooks() : void {
        add_action( 'init', [$this, 'load_textdomain'] );
        add_action( 'rest_api_init', [$this, 'register_rest_routes'] );
        // Phase 4: Scheduler (registers cron interval + hooks the digest action).
        Spellmatic_Scheduler::init();
        // Privacy policy suggestion (WordPress Privacy API).
        add_action( 'admin_init', ['Spellmatic_Harper', 'add_privacy_policy'] );
        if ( is_admin() ) {
            Spellmatic_Dashboard::get_instance();
        }
    }

    // ── i18n ──────────────────────────────────────────────────────────────────
    public function load_textdomain() : void {
        // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Not wp.org hosted; manual textdomain loading required.
        load_plugin_textdomain( 'spellmatic', false, dirname( SPELLMATIC_BASENAME ) . '/languages' );
    }

    // ── REST API routes ────────────────────────────────────────────────────────
    public function register_rest_routes() : void {
        // ── Scan endpoints ─────────────────────────────────────────────────
        register_rest_route( 'spellmatic/v1', '/scan/post/(?P<id>\\d+)', [
            'methods'             => 'GET',
            'callback'            => ['Spellmatic_Scanner', 'scan_post'],
            'permission_callback' => [$this, 'rest_permission'],
            'args'                => [
                'id' => [
                    'validate_callback' => fn( $v ) => is_numeric( $v ),
                    'sanitize_callback' => 'absint',
                ],
            ],
        ] );
        // Phase 2: start a browser-side scan — returns item list with plain text
        register_rest_route( 'spellmatic/v1', '/scan/start', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Scanner', 'scan_start'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Phase 2: browser submits detected errors; PHP stores them + returns results
        register_rest_route( 'spellmatic/v1', '/scan/submit', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Scanner', 'scan_submit'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Phase 1 legacy: synchronous full-site scan (kept for compatibility)
        register_rest_route( 'spellmatic/v1', '/scan/site', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Scanner', 'start_site_scan'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Get scan progress / results
        register_rest_route( 'spellmatic/v1', '/scan/status', [
            'methods'             => 'GET',
            'callback'            => ['Spellmatic_Scanner', 'get_scan_status'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Fix endpoints ──────────────────────────────────────────────────
        register_rest_route( 'spellmatic/v1', '/fix', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Fixer', 'apply_fix'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        register_rest_route( 'spellmatic/v1', '/fix/revert', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Fixer', 'revert_fix'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        register_rest_route( 'spellmatic/v1', '/fix/restore', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Fixer', 'restore_dismissed'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        register_rest_route( 'spellmatic/v1', '/results/export', [
            'methods'             => 'GET',
            'callback'            => ['Spellmatic_Fixer', 'export_csv'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Phase 4: Dictionary endpoints ──────────────────────────────────
        register_rest_route( 'spellmatic/v1', '/dictionary', [
            'methods'             => 'GET',
            'callback'            => ['Spellmatic_Dictionary', 'get_dictionary'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        register_rest_route( 'spellmatic/v1', '/dictionary/add', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Dictionary', 'add_word'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        register_rest_route( 'spellmatic/v1', '/dictionary/remove', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Dictionary', 'remove_word'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Phase 4: Clear all results ────────────────────────────────────
        register_rest_route( 'spellmatic/v1', '/results/clear', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Fixer', 'clear_results'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Phase 4: Digest endpoints ──────────────────────────────────────
        // Save digest settings and re-schedule cron.
        register_rest_route( 'spellmatic/v1', '/digest/settings', [
            'methods'             => 'POST',
            'callback'            => [$this, 'save_digest_settings'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Send a test digest email immediately.
        register_rest_route( 'spellmatic/v1', '/digest/test', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_Scheduler', 'send_test'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Phase 5: BYOK AI endpoints ─────────────────────────────────────
        // Run an AI-powered supplemental scan pass on a set of items.
        register_rest_route( 'spellmatic/v1', '/scan/ai', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_BYOK', 'scan_ai'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Provider connection status (masked keys, active provider).
        register_rest_route( 'spellmatic/v1', '/byok/status', [
            'methods'             => 'GET',
            'callback'            => ['Spellmatic_BYOK', 'status_via_rest'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Save (or delete) a provider API key via REST.
        register_rest_route( 'spellmatic/v1', '/byok/save', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_BYOK', 'save_via_rest'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Test a provider connection with a short sample text.
        register_rest_route( 'spellmatic/v1', '/byok/test', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_BYOK', 'test_via_rest'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Save AI scan toggle + max-items setting.
        register_rest_route( 'spellmatic/v1', '/byok/ai-settings', [
            'methods'             => 'POST',
            'callback'            => ['Spellmatic_BYOK', 'save_ai_settings_via_rest'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // ── Harper engine endpoints (revert to bundled) ──────────────────
        Spellmatic_Harper::register_routes();
        register_rest_route( 'spellmatic/v1', '/backup-dismissed', [
            'methods'             => 'POST',
            'callback'            => function () {
                update_option( 'spellmatic_backup_dismissed', true );
                return new WP_REST_Response([
                    'ok' => true,
                ]);
            },
            'permission_callback' => [$this, 'rest_permission'],
        ] );
        // Onboarding completion endpoint
        register_rest_route( 'spellmatic/v1', '/onboarding/complete', [
            'methods'             => 'POST',
            'callback'            => [$this, 'complete_onboarding'],
            'permission_callback' => [$this, 'rest_permission'],
        ] );
    }

    /**
     * Complete the onboarding wizard and save selected post types.
     * REST: POST /spellmatic/v1/onboarding/complete
     *
     * Body: { post_types: [...selectedTypes] }
     */
    public function complete_onboarding( WP_REST_Request $request ) : WP_REST_Response {
        $types = $request->get_param( 'post_types' ) ?? [];
        $settings = get_option( 'spellmatic_settings', self::default_settings() );
        // Reset all scan_* keys to false first
        foreach ( array_keys( $settings ) as $key ) {
            if ( strpos( $key, 'scan_' ) === 0 ) {
                $settings[$key] = false;
            }
        }
        // Set selected post types to true
        foreach ( $types as $type ) {
            $key = 'scan_' . sanitize_key( $type );
            $settings[$key] = true;
        }
        update_option( 'spellmatic_settings', $settings );
        update_option( 'spellmatic_onboarding_complete', true );
        return new WP_REST_Response([
            'ok' => true,
        ]);
    }

    /**
     * Save digest settings and reschedule the cron job.
     * REST: POST /spellmatic/v1/digest/settings
     *
     * Body: { enabled: bool, frequency: 'weekly'|'monthly', email: string }
     */
    public function save_digest_settings( WP_REST_Request $request ) : WP_REST_Response {
        $settings = get_option( 'spellmatic_settings', self::default_settings() );
        $enabled = (bool) $request->get_param( 'enabled' );
        $frequency = sanitize_key( $request->get_param( 'frequency' ) ?? 'weekly' );
        $email = sanitize_email( $request->get_param( 'email' ) ?? '' );
        if ( !in_array( $frequency, ['weekly', 'monthly'], true ) ) {
            $frequency = 'weekly';
        }
        $settings['digest_enabled'] = $enabled;
        $settings['digest_frequency'] = $frequency;
        $settings['digest_email'] = $email;
        update_option( 'spellmatic_settings', $settings );
        // Reschedule cron with new settings.
        Spellmatic_Scheduler::schedule();
        $next = wp_next_scheduled( Spellmatic_Scheduler::CRON_HOOK );
        return new WP_REST_Response([
            'saved'            => true,
            'digest_enabled'   => $enabled,
            'digest_frequency' => $frequency,
            'digest_email'     => $email,
            'next_scheduled'   => ( $next ? wp_date( 'Y-m-d H:i', $next ) : null ),
        ], 200);
    }

    // ── REST permission: only users who can manage options ────────────────────
    public function rest_permission() : bool {
        return current_user_can( 'manage_options' );
    }

    // ── Activation ────────────────────────────────────────────────────────────
    public static function activate() : void {
        self::create_tables();
        add_option( 'spellmatic_version', SPELLMATIC_VERSION );
        add_option( 'spellmatic_settings', self::default_settings() );
        flush_rewrite_rules();
        // Initialise scheduler on activation — picks up default settings (disabled).
        Spellmatic_Scheduler::schedule();
    }

    // ── Deactivation ──────────────────────────────────────────────────────────
    public static function deactivate() : void {
        Spellmatic_Scheduler::unschedule();
        flush_rewrite_rules();
    }

    // ── Default settings ──────────────────────────────────────────────────────
    public static function default_settings() : array {
        return [
            'scan_posts'        => true,
            'scan_pages'        => true,
            'scan_products'     => false,
            'scan_comments'     => false,
            'scan_meta'         => false,
            'engine'            => 'harper',
            'lt_url'            => '',
            'byok_provider'     => '',
            'byok_model'        => '',
            'custom_dictionary' => [],
            'digest_enabled'    => false,
            'digest_frequency'  => 'weekly',
            'ai_enabled'        => true,
            'ai_max_items'      => 5,
            'digest_email'      => '',
        ];
    }

    // ── Create custom DB tables ────────────────────────────────────────────────
    public static function create_tables() : void {
        global $wpdb;
        $charset = $wpdb->get_charset_collate();
        // Table: scan results
        $results_table = $wpdb->prefix . 'spellmatic_results';
        $sql_results = "CREATE TABLE IF NOT EXISTS {$results_table} (\n            id            BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n            post_id       BIGINT(20) UNSIGNED NOT NULL,\n            post_type     VARCHAR(50)  NOT NULL DEFAULT 'post',\n            field         VARCHAR(100) NOT NULL DEFAULT 'post_content',\n            error_type    VARCHAR(50)  NOT NULL DEFAULT 'spelling',\n            original      TEXT         NOT NULL,\n            suggestion    TEXT         NOT NULL,\n            context       TEXT         NOT NULL,\n            offset_start  INT(11)      NOT NULL DEFAULT 0,\n            offset_end    INT(11)      NOT NULL DEFAULT 0,\n            status        VARCHAR(20)  NOT NULL DEFAULT 'open',\n            scan_id       VARCHAR(36)  NOT NULL,\n            created_at    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,\n            updated_at    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n            PRIMARY KEY  (id),\n            KEY post_id   (post_id),\n            KEY scan_id   (scan_id),\n            KEY status    (status)\n        ) {$charset};";
        // Table: scan sessions (tracks full-site scan progress)
        $scans_table = $wpdb->prefix . 'spellmatic_scans';
        $sql_scans = "CREATE TABLE IF NOT EXISTS {$scans_table} (\n            id            VARCHAR(36)  NOT NULL,\n            status        VARCHAR(20)  NOT NULL DEFAULT 'pending',\n            total_items   INT(11)      NOT NULL DEFAULT 0,\n            scanned_items INT(11)      NOT NULL DEFAULT 0,\n            error_count   INT(11)      NOT NULL DEFAULT 0,\n            started_at    DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,\n            completed_at  DATETIME     NULL,\n            PRIMARY KEY  (id)\n        ) {$charset};";
        // Table: fix history (for undo / revert)
        $history_table = $wpdb->prefix . 'spellmatic_fix_history';
        $sql_history = "CREATE TABLE IF NOT EXISTS {$history_table} (\n            id            BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n            result_id     BIGINT(20) UNSIGNED NOT NULL,\n            post_id       BIGINT(20) UNSIGNED NOT NULL,\n            field         VARCHAR(100) NOT NULL,\n            original_word TEXT         NOT NULL,\n            replacement   TEXT         NOT NULL,\n            before_content LONGTEXT    NOT NULL,\n            fixed_at      DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,\n            reverted      TINYINT(1)   NOT NULL DEFAULT 0,\n            PRIMARY KEY  (id),\n            KEY result_id (result_id),\n            KEY post_id   (post_id)\n        ) {$charset};";
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql_results );
        dbDelta( $sql_scans );
        dbDelta( $sql_history );
    }

}
