<?php
/**
 * Plugin Name: Niwa AI
 * Plugin URI: https://example.com/niwa-ai
 * Description: AI sales assistant for WooCommerce with fast retrieval and product recommendations.
 * Version: 0.13.874
 * Requires at least: 6.5
 * Requires PHP: 8.1
 * Author: Niwa AI
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: niwa-ai
 */

if ( ! function_exists( 'niwa_ai_run_silently' ) ) {
    function niwa_ai_run_silently( $callback ) {
        $level = ob_get_level();
        ob_start();
        try {
            $callback();
        } finally {
            while ( ob_get_level() > $level ) {
                ob_end_clean();
            }
        }
    }
}

if ( ! function_exists( 'na_fs' ) ) {
    // Create a helper function for easy SDK access.
    function na_fs() {
        global $na_fs;

        if ( ! isset( $na_fs ) ) {
            // Activate multisite network integration.
            if ( ! defined( 'WP_FS__PRODUCT_25824_MULTISITE' ) ) {
                define( 'WP_FS__PRODUCT_25824_MULTISITE', true );
            }

            // Include Freemius SDK.
            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';

            $na_fs = fs_dynamic_init( array(
                'id'                  => '25824',
                'slug'                => 'niwa-ai',
                'premium_slug'        => 'niwa-ai-pro',
                'type'                => 'plugin',
                'public_key'          => 'pk_b2b40497f0af60f3f6455bc6fa1c9',
                'is_premium'          => true,
                'is_premium_only'     => true,
                'has_addons'          => false,
                'has_paid_plans'      => true,
                'is_org_compliant'    => false,
                'trial'               => array(
                    'days'               => 30,
                    'is_require_payment' => true,
                ),
                'menu'                => array(
                    'slug'           => 'niwa-ai',
                    'contact'        => false,
                    'support'        => false,
                ),
            ) );
        }

        return $na_fs;
    }

    niwa_ai_run_silently( function() {
        // Init Freemius.
        na_fs();
        // Signal that SDK was initiated.
        do_action( 'na_fs_loaded' );
    } );
}

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

if ( defined( 'NIWA_AI_LOADED' ) ) {
    return;
}
define( 'NIWA_AI_LOADED', true );

if ( ! defined( 'NIWA_AI_VERSION' ) ) {
define( 'NIWA_AI_VERSION', '0.13.874' );
}
if ( ! defined( 'NIWA_AI_FILE' ) ) {
    define( 'NIWA_AI_FILE', __FILE__ );
}
if ( ! defined( 'NIWA_AI_DIR' ) ) {
    define( 'NIWA_AI_DIR', plugin_dir_path( __FILE__ ) );
}
if ( ! defined( 'NIWA_AI_URL' ) ) {
    define( 'NIWA_AI_URL', plugin_dir_url( __FILE__ ) );
}
if ( ! defined( 'NIWA_AI_DEFAULT_BACKEND_AVATAR_URL' ) ) {
    define( 'NIWA_AI_DEFAULT_BACKEND_AVATAR_URL', NIWA_AI_URL . 'assets/img/niwa-admin-personal-assistant.png' );
}
if ( ! defined( 'NIWA_AI_DEFAULT_FRONTEND_AVATAR_URL' ) ) {
    define( 'NIWA_AI_DEFAULT_FRONTEND_AVATAR_URL', NIWA_AI_DEFAULT_BACKEND_AVATAR_URL );
}

function niwa_ai_guard_vendor_integrity_monitor_file() {
    $rel_path = 'includes/admin-autonomy/class-admin-autonomy-vendor-integrity-monitor.php';
    $file     = NIWA_AI_DIR . $rel_path;
    if ( ! is_file( $file ) || ! is_readable( $file ) ) {
        return;
    }

    $hash = hash_file( 'sha256', $file );
    if ( ! is_string( $hash ) || '' === $hash ) {
        return;
    }

    $option = 'niwa_ai_admin_autonomy_vendor_monitor_guard';
    $state  = get_option( $option, array() );
    $state  = is_array( $state ) ? $state : array();
    if ( empty( $state['sha256'] ) ) {
        update_option(
            $option,
            array(
                'path'           => $rel_path,
                'sha256'         => $hash,
                'plugin_version' => NIWA_AI_VERSION,
                'initialized_at' => time(),
            ),
            false
        );
        return;
    }

    if ( hash_equals( (string) $state['sha256'], $hash ) ) {
        return;
    }

    if ( ! empty( $state['plugin_version'] ) && (string) $state['plugin_version'] !== NIWA_AI_VERSION ) {
        $state['previous_sha256'] = (string) $state['sha256'];
        $state['sha256']         = $hash;
        $state['plugin_version'] = NIWA_AI_VERSION;
        $state['refreshed_at']   = time();
        update_option( $option, $state, false );
        return;
    }

    $corrupt_path = $file . '.corrupt-' . gmdate( 'Ymd-His' ) . '.php';
    $moved        = @rename( $file, $corrupt_path );
    $deleted      = $moved && is_file( $corrupt_path ) ? @unlink( $corrupt_path ) : @unlink( $file );
    $locked_until = time() + DAY_IN_SECONDS;

    update_option( 'niwa_ai_admin_autonomy_lock_vendor_integrity_response', (string) $locked_until, false );
    niwa_ai_record_vendor_integrity_monitor_corruption( 'vendor_monitor_file_guard_hash_drift' );
    update_option(
        'niwa_ai_admin_autonomy_vendor_lock_status',
        array(
            'locked'              => true,
            'changed'             => true,
            'source'              => 'vendor_monitor_file_guard',
            'lock_name'           => 'vendor_integrity_response',
            'locked_until'        => $locked_until,
            'detected_at'         => time(),
            'path'                => $rel_path,
            'previous_digest'     => (string) $state['sha256'],
            'digest'              => $hash,
            'corrupt_path'        => $moved ? $corrupt_path : '',
            'deleted_corrupt_file' => (bool) $deleted,
            'file_exists_after'   => is_file( $file ),
        ),
        false
    );

    if ( function_exists( 'wp_clear_scheduled_hook' ) ) {
        wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_check_vendor_integrity' );
    }
}
niwa_ai_guard_vendor_integrity_monitor_file();

function niwa_ai_record_vendor_integrity_monitor_corruption( $source = 'vendor_integrity_monitor_missing' ) {
    $rel_path     = 'includes/admin-autonomy/class-admin-autonomy-vendor-integrity-monitor.php';
    $locked_until = time() + DAY_IN_SECONDS;
    $status       = array(
        'blocked'       => true,
        'corrupted'     => true,
        'source'        => sanitize_key( (string) $source ),
        'reason'        => 'vendor_integrity_monitor_missing',
        'path'          => $rel_path,
        'detected_at'   => time(),
        'locked_until'  => $locked_until,
        'plugin_version' => NIWA_AI_VERSION,
    );

    update_option( 'niwa_ai_admin_autonomy_plugin_corrupt_status', $status, false );
    update_option( 'niwa_ai_admin_autonomy_lock_vendor_integrity_response', (string) $locked_until, false );
    update_option(
        'niwa_ai_admin_autonomy_vendor_lock_status',
        array(
            'locked'       => true,
            'changed'      => true,
            'source'       => $status['source'],
            'lock_name'    => 'vendor_integrity_response',
            'locked_until' => $locked_until,
            'detected_at'  => $status['detected_at'],
            'path'         => $rel_path,
            'error'        => $status['reason'],
        ),
        false
    );

    return $status;
}

function niwa_ai_should_block_corrupt_plugin() {
    $status = get_option( 'niwa_ai_admin_autonomy_plugin_corrupt_status', array() );
    $status = is_array( $status ) ? $status : array();
    if ( empty( $status['blocked'] ) || 'vendor_integrity_monitor_missing' !== (string) ( $status['reason'] ?? '' ) ) {
        return false;
    }

    $rel_path = isset( $status['path'] ) ? (string) $status['path'] : 'includes/admin-autonomy/class-admin-autonomy-vendor-integrity-monitor.php';
    if ( is_file( NIWA_AI_DIR . $rel_path ) ) {
        delete_option( 'niwa_ai_admin_autonomy_plugin_corrupt_status' );
        return false;
    }

    return true;
}

if ( niwa_ai_should_block_corrupt_plugin() ) {
    add_action( 'admin_notices', function() {
        echo '<div class="notice notice-error"><p><strong>Niwa AI:</strong> Plugin is blocked as corrupted because the admin autonomy vendor integrity monitor file is missing. Reinstall a clean Niwa AI package.</p></div>';
    } );
    return;
}

function niwa_ai_check_vendor_integrity_monitor_file_presence() {
    $file = NIWA_AI_DIR . 'includes/admin-autonomy/class-admin-autonomy-vendor-integrity-monitor.php';
    if ( is_file( $file ) ) {
        return array(
            'success' => true,
            'exists'  => true,
        );
    }

    return niwa_ai_record_vendor_integrity_monitor_corruption( 'daily_vendor_monitor_file_presence_check' );
}
add_action( 'niwa_ai_admin_autonomy_check_vendor_integrity', 'niwa_ai_check_vendor_integrity_monitor_file_presence', 1 );

function niwa_ai_ensure_vendor_integrity_monitor_file_cron() {
    if ( ! wp_next_scheduled( 'niwa_ai_admin_autonomy_check_vendor_integrity' ) ) {
        wp_schedule_event( time() + 300, 'daily', 'niwa_ai_admin_autonomy_check_vendor_integrity' );
    }
}
add_action( 'init', 'niwa_ai_ensure_vendor_integrity_monitor_file_cron', 20 );

// Composer autoloader — vendor dependencies (XLSXWriter, etc.).
$composer_autoload = NIWA_AI_DIR . 'vendor/autoload.php';
if ( file_exists( $composer_autoload ) ) {
    require_once $composer_autoload;
}

// Autoloader — replaces all manual require_once calls.
$niwa_autoloader = NIWA_AI_DIR . 'includes/class-autoloader.php';
if ( ! file_exists( $niwa_autoloader ) ) {
    add_action( 'admin_notices', function() {
        echo '<div class="notice notice-error"><p><strong>Niwa AI:</strong> Missing autoloader. Please reinstall plugin package.</p></div>';
    } );
    return;
}
require_once $niwa_autoloader;
Niwa_AI_Autoloader::register();

$niwa_uninstaller = NIWA_AI_DIR . 'includes/core/uninstaller.php';
if ( file_exists( $niwa_uninstaller ) ) {
    require_once $niwa_uninstaller;
    niwa_ai_register_freemius_uninstaller();
}

if ( ! function_exists( 'niwa_ai_register_cron_schedules' ) ) {

/**
 * Register Niwa AI custom cron schedules at the earliest possible moment.
 *
 * Previously this was registered inside `Niwa_AI_Plugin::boot()` (via the
 * `cron_schedules` filter add inside the boot routine). The problem with that
 * placement is that if `boot()` fatals for any reason (missing class, partial
 * upload, opcache stale), the filter never gets added — and WP-Cron then
 * floods debug.log with `invalid_schedule` errors for every previously
 * scheduled niwa_ai_thirty_seconds / niwa_ai_one_minute / niwa_ai_five_minutes
 * event, on every single request. By registering the filter as a free
 * function before `plugins_loaded` we guarantee the schedules exist even when
 * the rest of the plugin is unable to boot, so wp-cron stops thrashing and
 * the debug.log stops drowning unrelated warnings.
 *
 * @param array<string, array<string, mixed>> $schedules
 * @return array<string, array<string, mixed>>
 */
function niwa_ai_register_cron_schedules( $schedules ) {
    if ( ! is_array( $schedules ) ) {
        $schedules = array();
    }

    // Unconditionally register every Niwa schedule slug that any part of the
    // plugin can hand to `wp_schedule_event`. We used to gate this behind a
    // `class_exists('Niwa_AI_Performance_Settings')` check, but that opened
    // a race window: if the autoloader, opcache, or `plugins_loaded` boot
    // path was even mildly stale, `class_exists` returned false and we fell
    // back to registering only three slugs — meaning any pre-existing
    // cron event keyed to one of the missing slugs (e.g. `niwa_ai_two_minutes`
    // saved by a previous Performance Settings save) instantly failed with
    // `invalid_schedule` on every wp-cron tick and flooded debug.log.
    // Registering everything inline removes that dependency entirely.
    static $niwa_definitions = array(
        'niwa_ai_thirty_seconds'  => array( 'interval' => 30,   'display' => 'Niwa AI every 30 seconds' ),
        'niwa_ai_one_minute'      => array( 'interval' => 60,   'display' => 'Niwa AI every 1 minute' ),
        'niwa_ai_two_minutes'     => array( 'interval' => 120,  'display' => 'Niwa AI every 2 minutes' ),
        'niwa_ai_five_minutes'    => array( 'interval' => 300,  'display' => 'Niwa AI every 5 minutes' ),
        'niwa_ai_ten_minutes'     => array( 'interval' => 600,  'display' => 'Niwa AI every 10 minutes' ),
        'niwa_ai_fifteen_minutes' => array( 'interval' => 900,  'display' => 'Niwa AI every 15 minutes' ),
        'niwa_ai_thirty_minutes'  => array( 'interval' => 1800, 'display' => 'Niwa AI every 30 minutes' ),
    );

    foreach ( $niwa_definitions as $slug => $definition ) {
        if ( ! isset( $schedules[ $slug ] ) ) {
            $schedules[ $slug ] = $definition;
        }
    }

    return $schedules;
}
// Priority 1 so we run before WP core's default filters and before any other
// plugin that might want to filter `cron_schedules` later — we want our slugs
// in the array no matter who else hooks in.
add_filter( 'cron_schedules', 'niwa_ai_register_cron_schedules', 1 );

/**
 * Eagerly require critical classes that `Niwa_AI_Plugin::boot()` instantiates
 * unconditionally and without `class_exists` guards. Belt-and-braces fallback:
 * if for any reason the autoloader does not resolve these (opcache holding a
 * stale classmap, partial FTP upload, host-level filesystem quirk), this gives
 * us a hard local require to fall back on. file_exists keeps it safe — if the
 * file genuinely isn't there, we skip silently and let the admin notice in
 * niwa_ai_boot() surface a readable error instead of WSOD.
 *
 * @return void
 */
function niwa_ai_preload_critical_classes() {
    $critical = array(
        'Niwa_AI_Constants'        => 'includes/core/class-constants.php',
        'Niwa_AI_Content_Store'    => 'includes/core/class-content-store.php',
        'Niwa_AI_Contact_Service'  => 'includes/core/class-contact-service.php',
        'Niwa_AI_Woo_RAG_Provider' => 'includes/core/class-woo-rag-provider.php',
        'Niwa_AI_RAG_Fallback'     => 'includes/core/class-rag-fallback.php',
    );

    foreach ( $critical as $class_name => $rel_path ) {
        if ( class_exists( $class_name, false ) ) {
            continue;
        }
        $file = NIWA_AI_DIR . $rel_path;
        if ( file_exists( $file ) ) {
            require_once $file;
        }
    }
}
niwa_ai_preload_critical_classes();

function niwa_ai_schedule_admin_autonomy_poll_restart_event( $reason = 'manual' ) {
    $reason = sanitize_key( (string) $reason );
    if ( '' === $reason ) {
        $reason = 'manual';
    }

    $args = array( $reason );
    if ( ! wp_next_scheduled( 'niwa_ai_admin_autonomy_restart_polling_runtime', $args ) ) {
        wp_schedule_single_event( time() + 5, 'niwa_ai_admin_autonomy_restart_polling_runtime', $args );
    }
}

function niwa_ai_queue_admin_autonomy_poll_restart( $reason = 'manual' ) {
    $reason = sanitize_key( (string) $reason );
    if ( '' === $reason ) {
        $reason = 'manual';
    }

    set_transient( 'niwa_ai_admin_autonomy_restart_polling', $reason, 10 * MINUTE_IN_SECONDS );
    niwa_ai_schedule_admin_autonomy_poll_restart_event( $reason );
}

function niwa_ai_ensure_recurring_cron_event( $hook, $schedule, $offset = 60, $args = array() ) {
    if ( class_exists( 'Niwa_AI_Cron_Maintenance' ) ) {
        return Niwa_AI_Cron_Maintenance::ensure_recurring_event( $hook, $schedule, $offset, $args );
    }

    if ( ! wp_next_scheduled( $hook, $args ) ) {
        return false !== wp_schedule_event( time() + max( 1, intval( $offset ) ), $schedule, $hook, $args );
    }

    return true;
}

function niwa_ai_schedule_frontend_state_reset() {
    update_option( Niwa_AI_Constants::OPT_CLEANUP_LOCALSTORAGE, time(), false );
}

function niwa_ai_purge_site_caches() {
    wp_cache_delete( 'alloptions', 'options' );
    wp_cache_delete( 'notoptions', 'options' );

    if ( function_exists( 'wp_cache_flush' ) ) {
        wp_cache_flush();
    }

    if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->files_manager ) ) {
        try {
            \Elementor\Plugin::$instance->files_manager->clear_cache();
        } catch ( Throwable $e ) {
            // Ignore third-party cache purge failures.
        }
    }

    if ( class_exists( '\LiteSpeed\Purge' ) ) {
        try {
            \LiteSpeed\Purge::purge_all();
        } catch ( Throwable $e ) {
            // Ignore third-party cache purge failures.
        }
    } else {
        do_action( 'litespeed_purge_all' );
    }

    if ( function_exists( 'rocket_clean_domain' ) ) {
        rocket_clean_domain();
    }

    if ( function_exists( 'w3tc_flush_all' ) ) {
        w3tc_flush_all();
    }

    if ( class_exists( 'autoptimizeCache' ) && method_exists( 'autoptimizeCache', 'clearall' ) ) {
        \autoptimizeCache::clearall();
    }

    if ( function_exists( 'wpfc_clear_all_cache' ) ) {
        wpfc_clear_all_cache();
    }

    if ( function_exists( 'sg_cachepress_purge_everything' ) ) {
        sg_cachepress_purge_everything();
    }

    do_action( 'niwa_ai_after_cache_purge' );
}

function niwa_ai_activate() {
	if ( false === get_option( Niwa_AI_Constants::OPT_SETTINGS, false ) ) {
		add_option(
			Niwa_AI_Constants::OPT_SETTINGS,
			array( 'model' => Niwa_AI_Constants::DEFAULT_FRONTEND_MODEL ),
			'',
			false
		);
	}

    // All classes resolved via autoloader — no manual requires needed.
    $content_store = new Niwa_AI_Content_Store();
    $content_store->ensure_tables();
    $content_store->migrate_from_option();

    $worker = new Niwa_AI_Index_Worker( $content_store );
    $worker->handle_rebuild_full();

    $contact_service = new Niwa_AI_Contact_Service();
    $contact_service->ensure_table_schema();

	$appointment_service = new Niwa_AI_Appointment_Service( $contact_service );
	$appointment_service->ensure_schema( true );

    $cb = new Niwa_AI_Conversion_Busters();
    $cb->create_analytics_table();

    $ac = new Niwa_AI_Attention_Capture();
    $ac->create_analytics_table();

    $analytics = new Niwa_AI_Analytics_Dashboard();
    $analytics->create_events_table();

    Niwa_AI_Traffic_Module::activate();

    niwa_ai_ensure_recurring_cron_event( Niwa_AI_Conversion_Busters::CLEANUP_HOOK, 'hourly', 60 );

    $autonomy_store = new Niwa_AI_Admin_Autonomy_Store();
    $autonomy_store->ensure_table_schema();

    $autonomy_scheduler = new Niwa_AI_Admin_Autonomy_Scheduler();
    $autonomy_scheduler->ensure_table_schema();

    $had_existing_niwa_state = get_option( Niwa_AI_Constants::OPT_DB_SCHEMA_VERSION, false )
        || get_option( Niwa_AI_Constants::OPT_AUTONOMY_RUNTIME_VERSION, false )
        || get_option( Niwa_AI_Constants::OPT_SETTINGS, false )
        || get_option( Niwa_AI_Admin_Autonomy_Telegram::OPTION_KEY, false );
    if ( ! $had_existing_niwa_state && ! get_option( Niwa_AI_Constants::OPT_AUTONOMY_INSTALLED_AT, 0 ) ) {
        update_option( Niwa_AI_Constants::OPT_AUTONOMY_INSTALLED_AT, time(), false );
    }

    $session_memory_store = new Niwa_AI_Session_Memory_Store();
    $session_memory_store->ensure_table_schema();

    $question_log = new Niwa_AI_Frontend_Question_Log();
    $question_log->ensure_table_schema();

    if ( ! wp_next_scheduled( 'niwa_ai_process_frontend_chat_queue' ) ) {
        wp_schedule_single_event( time() + 45, 'niwa_ai_process_frontend_chat_queue' );
    }

    // Background tick scheduling on activation: honor Performance Settings if
    // the class is already loaded by autoload. Crucially, we no longer force
    // a reschedule when the existing slug doesn't match `niwa_ai_one_minute`
    // — that "fix-up" check was the reason Performance Settings appeared to
    // do nothing for background tick on subsequent boots.
    $slug = class_exists( 'Niwa_AI_Performance_Settings' )
        ? Niwa_AI_Performance_Settings::resolve_schedule_for_hook( Niwa_AI_Admin_Autonomy::BACKGROUND_TICK_HOOK, 'niwa_ai_one_minute' )
        : 'niwa_ai_one_minute';
    niwa_ai_ensure_recurring_cron_event( Niwa_AI_Admin_Autonomy::BACKGROUND_TICK_HOOK, $slug, 60 );

    $slug = class_exists( 'Niwa_AI_Performance_Settings' )
        ? Niwa_AI_Performance_Settings::resolve_schedule_for_hook( 'niwa_ai_admin_autonomy_process_scheduled_tasks', 'niwa_ai_thirty_seconds' )
        : 'niwa_ai_thirty_seconds';
    niwa_ai_ensure_recurring_cron_event( 'niwa_ai_admin_autonomy_process_scheduled_tasks', $slug, 30 );

    niwa_ai_ensure_recurring_cron_event( 'niwa_ai_admin_autonomy_background_clock_watchdog', 'niwa_ai_one_minute', 30 );

    niwa_ai_ensure_recurring_cron_event( 'niwa_ai_admin_autonomy_imap_idle_watchdog', 'niwa_ai_five_minutes', 90 );

    niwa_ai_ensure_recurring_cron_event( Niwa_AI_Admin_Autonomy::WHATSAPP_GATEWAY_WATCHDOG_HOOK, 'niwa_ai_one_minute', 60 );

    niwa_ai_ensure_recurring_cron_event( Niwa_AI_Admin_Autonomy::VENDOR_INTEGRITY_HOOK, 'daily', 300 );

    if ( class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
        $clock = new Niwa_AI_Admin_Autonomy_Background_Clock();
        $clock->maybe_start( 'plugin-activation', true );
    }

    niwa_ai_schedule_frontend_state_reset();
    niwa_ai_purge_site_caches();
    niwa_ai_queue_admin_autonomy_poll_restart( 'plugin-activation' );
}
register_activation_hook( __FILE__, 'niwa_ai_activate' );

function niwa_ai_deactivate() {
    wp_clear_scheduled_hook( 'niwa_ai_cb_cleanup_expired' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_poll_telegram' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_process_scheduled_tasks' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_process_scheduled_job' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_process_turn_queue' );
    wp_clear_scheduled_hook( 'niwa_ai_process_admin_ui_chat_queue' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_imap_idle_watchdog' );
    wp_clear_scheduled_hook( 'niwa_ai_admin_autonomy_background_clock_watchdog' );
    wp_clear_scheduled_hook( Niwa_AI_Admin_Autonomy::WHATSAPP_GATEWAY_WATCHDOG_HOOK );
    wp_clear_scheduled_hook( Niwa_AI_Admin_Autonomy::WHATSAPP_TEXT_BATCH_FLUSH_HOOK );
    wp_clear_scheduled_hook( Niwa_AI_Admin_Autonomy::BACKGROUND_TICK_HOOK );
    wp_clear_scheduled_hook( Niwa_AI_Admin_Autonomy::VENDOR_INTEGRITY_HOOK );
    wp_clear_scheduled_hook( 'niwa_ai_process_frontend_chat_queue' );
    wp_clear_scheduled_hook( 'niwa_ai_site_knowledge_reindex_deferred' );
    wp_clear_scheduled_hook( Niwa_AI_Agent_Profile_Summary_Service::REFRESH_HOOK );
    wp_clear_scheduled_hook( Niwa_AI_Traffic_Rollup_Worker::ROLLUP_HOOK );
    wp_clear_scheduled_hook( Niwa_AI_Traffic_Rollup_Worker::CLEANUP_HOOK );

    // Cart Recovery: cancel Action Scheduler + WP-Cron registrations.
    // This includes the recurring tick and per-row mail actions, across both
    // the current AS group and legacy ungrouped actions from older builds.
    wp_clear_scheduled_hook( Niwa_AI_Constants::CRON_HOOK_CART_RECOVERY_TICK );
    wp_clear_scheduled_hook( Niwa_AI_Cart_Recovery_Scheduler::ACTION_ROW_FIRE_MAIL1 );
    wp_clear_scheduled_hook( Niwa_AI_Cart_Recovery_Scheduler::ACTION_ROW_FIRE_MAIL2 );
    if ( function_exists( 'as_unschedule_all_actions' ) ) {
        Niwa_AI_Cart_Recovery_Scheduler::unschedule_all_cart_recovery_actions( true );
    }

    update_option( Niwa_AI_Constants::OPT_ADMIN_UI_CHAT_QUEUE, array(), false );
    if ( class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
        Niwa_AI_Admin_Autonomy_Background_Clock::request_stop( 'plugin-deactivation' );
    }
    niwa_ai_purge_site_caches();
}
register_deactivation_hook( __FILE__, 'niwa_ai_deactivate' );

function niwa_ai_handle_plugin_update( $upgrader, $hook_extra ) {
    if ( ! is_array( $hook_extra ) ) {
        return;
    }

    if ( empty( $hook_extra['action'] ) || 'update' !== $hook_extra['action'] ) {
        return;
    }

    if ( empty( $hook_extra['type'] ) || 'plugin' !== $hook_extra['type'] ) {
        return;
    }

    $plugins = isset( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ? $hook_extra['plugins'] : array();
    if ( in_array( plugin_basename( __FILE__ ), $plugins, true ) ) {
        niwa_ai_schedule_frontend_state_reset();
        niwa_ai_purge_site_caches();
        niwa_ai_queue_admin_autonomy_poll_restart( 'plugin-update' );
        if ( class_exists( 'Niwa_AI_Admin_Autonomy_Background_Clock' ) ) {
            $clock = new Niwa_AI_Admin_Autonomy_Background_Clock();
            $clock->maybe_start( 'plugin-update', true );
        }
    }
}
add_action( 'upgrader_process_complete', 'niwa_ai_handle_plugin_update', 10, 2 );

function niwa_ai_maybe_restart_admin_autonomy_polling() {
    $reason = get_transient( 'niwa_ai_admin_autonomy_restart_polling' );
    if ( false === $reason ) {
        return;
    }

    delete_transient( 'niwa_ai_admin_autonomy_restart_polling' );
    niwa_ai_schedule_admin_autonomy_poll_restart_event( $reason );
}
add_action( 'admin_init', 'niwa_ai_maybe_restart_admin_autonomy_polling', 20 );

function niwa_ai_boot() {
    try {
        $plugin = new Niwa_AI_Plugin();
        $plugin->boot();
        $GLOBALS['niwa_ai_plugin_instance'] = $plugin;
    } catch ( Throwable $e ) {
        // A fatal during boot would otherwise take down the entire WP request
        // (admin AND frontend), making the site unusable until the plugin is
        // manually disabled via FTP. Catching here keeps wp-admin clickable so
        // the site owner can investigate or deactivate the plugin from the UI.
        $msg = sprintf(
            '[Niwa AI] boot failed: %s in %s:%d',
            $e->getMessage(),
            $e->getFile(),
            (int) $e->getLine()
        );
        error_log( $msg );

        add_action( 'admin_notices', function () use ( $e ) {
            if ( ! current_user_can( 'activate_plugins' ) ) {
                return;
            }
            echo '<div class="notice notice-error"><p><strong>Niwa AI:</strong> ';
            echo esc_html(
                sprintf(
                    'Plugin nije uspeo da se učita — %s u %s:%d. Backend ostaje upotrebljiv. Pokušaj reinstalaciju plugin-a ili ga deaktiviraj.',
                    $e->getMessage(),
                    basename( $e->getFile() ),
                    (int) $e->getLine()
                )
            );
            echo '</p></div>';
        } );
    }
}
add_action( 'plugins_loaded', 'niwa_ai_boot' );

}
