<?php
/**
 * LuxStrat International — Universal Scheduler
 *
 * One class for ALL languages. Each language gets its own cron hook:
 *   dba_luxstrat_{lang}_cron
 *
 * Schedule (days + hour) comes from the language config.
 *
 * @package DamalionBlogAI
 * @subpackage LuxStratIntl
 */

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

class DBA_LuxStrat_Intl_Scheduler {

    const TIMEZONE = 'Europe/Luxembourg';

    /** @var array All loaded language configs (code => config) */
    private $languages = array();

    private static $instance = null;

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

    private function __construct() {
        // Load all language configs
        $this->languages = DBA_LuxStrat_Intl_Loader::get_languages();

        add_filter( 'cron_schedules', array( $this, 'register_schedules' ) );

        // Register cron callback for each language
        foreach ( $this->languages as $lang => $config ) {
            $hook = 'dba_luxstrat_' . $lang . '_cron';
            add_action( $hook, function() use ( $lang, $config ) {
                $this->run( $lang, $config );
            } );
        }

        // Self-healing catch-up on every page load
        add_action( 'init', array( $this, 'maybe_catch_up_all' ), 100 );

        // Ensure cron registered on admin load
        add_action( 'admin_init', array( $this, 'ensure_all_scheduled' ) );
    }

    /* ──────────────────────────────────────────────────
     *  SCHEDULES
     * ────────────────────────────────────────────────── */

    public function register_schedules( $schedules ) {
        if ( ! isset( $schedules['daily'] ) ) {
            $schedules['daily'] = array( 'interval' => 86400, 'display' => 'Once Daily' );
        }
        return $schedules;
    }

    /* ──────────────────────────────────────────────────
     *  ACTIVATE / DEACTIVATE (call for each language)
     * ────────────────────────────────────────────────── */

    public static function activate_language( $lang, $lang_config ) {
        $hook = 'dba_luxstrat_' . $lang . '_cron';

        if ( ! isset( wp_get_schedules()['daily'] ) ) {
            add_filter( 'cron_schedules', function( $s ) {
                $s['daily'] = array( 'interval' => 86400, 'display' => 'Once Daily' );
                return $s;
            } );
        }

        if ( ! wp_next_scheduled( $hook ) ) {
            $next = self::next_target_timestamp( $lang_config );
            wp_schedule_event( $next, 'daily', $hook );
            error_log( 'LuxStrat[' . $lang . ']: Cron scheduled — next: ' . gmdate( 'Y-m-d H:i:s', $next ) . ' UTC' );
        }
    }

    public static function deactivate_language( $lang ) {
        wp_clear_scheduled_hook( 'dba_luxstrat_' . $lang . '_cron' );
        error_log( 'LuxStrat[' . $lang . ']: Cron removed.' );
    }

    /** Activate all loaded languages. */
    public static function activate_all() {
        $languages = DBA_LuxStrat_Intl_Loader::get_languages();
        foreach ( $languages as $lang => $config ) {
            $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
            if ( ! empty( $settings['enabled'] ) ) {
                self::activate_language( $lang, $config );
            }
        }
    }

    /** Deactivate all loaded languages. */
    public static function deactivate_all() {
        $languages = DBA_LuxStrat_Intl_Loader::get_languages();
        foreach ( $languages as $lang => $config ) {
            self::deactivate_language( $lang );
        }
    }

    public function ensure_all_scheduled() {
        foreach ( $this->languages as $lang => $config ) {
            $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
            if ( empty( $settings['enabled'] ) ) continue;
            $hook = 'dba_luxstrat_' . $lang . '_cron';
            if ( ! wp_next_scheduled( $hook ) ) {
                self::activate_language( $lang, $config );
                error_log( 'LuxStrat[' . $lang . ']: Cron missing — rescheduled.' );
            }
        }
    }

    /* ──────────────────────────────────────────────────
     *  NEXT TARGET TIMESTAMP
     * ────────────────────────────────────────────────── */

    private static function next_target_timestamp( $lang_config ) {
        $days = ! empty( $lang_config['schedule']['days'] ) ? $lang_config['schedule']['days'] : array( 1 );
        $hour = ! empty( $lang_config['schedule']['hour'] ) ? (int) $lang_config['schedule']['hour'] : 7;

        try {
            $tz  = new DateTimeZone( self::TIMEZONE );
            $now = new DateTime( 'now', $tz );

            // Check today
            $day_n = (int) $now->format( 'N' );
            if ( in_array( $day_n, $days, true ) ) {
                $today = clone $now;
                $today->setTime( $hour, 0, 0 );
                if ( $today > $now ) return $today->getTimestamp();
            }

            // Find next target day
            $candidate = clone $now;
            for ( $i = 1; $i <= 7; $i++ ) {
                $candidate->modify( '+1 day' );
                if ( in_array( (int) $candidate->format( 'N' ), $days, true ) ) {
                    $candidate->setTime( $hour, 0, 0 );
                    return $candidate->getTimestamp();
                }
            }
        } catch ( \Exception $e ) {}

        return time() + 3600;
    }

    /* ──────────────────────────────────────────────────
     *  SELF-HEALING CATCH-UP (all languages)
     * ────────────────────────────────────────────────── */

    public function maybe_catch_up_all() {
        foreach ( $this->languages as $lang => $config ) {
            $this->maybe_catch_up( $lang, $config );
        }
    }

    private function maybe_catch_up( $lang, $lang_config ) {
        $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
        if ( empty( $settings['enabled'] ) ) return;

        $days = ! empty( $lang_config['schedule']['days'] ) ? $lang_config['schedule']['days'] : array();
        $hour = ! empty( $lang_config['schedule']['hour'] ) ? (int) $lang_config['schedule']['hour'] : 7;

        try {
            $tz  = new DateTimeZone( self::TIMEZONE );
            $now = new DateTime( 'now', $tz );
        } catch ( \Exception $e ) { return; }

        if ( ! in_array( (int) $now->format( 'N' ), $days, true ) ) return;
        if ( (int) $now->format( 'G' ) < $hour ) return;

        $lock_key = 'dba_luxstrat_' . $lang . '_ran_' . $now->format( 'Y-m-d' );
        if ( get_transient( $lock_key ) ) return;

        error_log( 'LuxStrat[' . $lang . '] catch-up: ' . $now->format( 'l H:i' ) . ' CET — running now.' );
        set_transient( $lock_key, true, DAY_IN_SECONDS );

        // Capture $lang/$config for the closure
        $captured_lang   = $lang;
        $captured_config = $lang_config;

        add_action( 'shutdown', function() use ( $captured_lang, $captured_config ) {
            ignore_user_abort( true );
            if ( function_exists( 'set_time_limit' ) ) @set_time_limit( 300 );

            // v2.15.0: Global conflict lock
            $hook = 'dba_luxstrat_' . $captured_lang . '_cron';
            if ( class_exists( 'DBA_Conflict_Center' ) && ! DBA_Conflict_Center::acquire( 'luxstrat_' . $captured_lang, $hook ) ) {
                // Clear daily lock so catch-up retries on next page load
                try {
                    $tz  = new DateTimeZone( 'Europe/Luxembourg' );
                    $now = new DateTime( 'now', $tz );
                    delete_transient( 'dba_luxstrat_' . $captured_lang . '_ran_' . $now->format( 'Y-m-d' ) );
                } catch ( \Exception $e ) {}
                error_log( 'LuxStrat[' . $captured_lang . '] catch-up: Another module running — will retry' );
                return;
            }

            $result = DBA_LuxStrat_Intl_Generator::run( $captured_lang, $captured_config );

            if ( is_wp_error( $result ) ) {
                error_log( 'LuxStrat[' . $captured_lang . '] catch-up error: ' . $result->get_error_message() );
                try {
                    $tz  = new DateTimeZone( 'Europe/Luxembourg' );
                    $now = new DateTime( 'now', $tz );
                    delete_transient( 'dba_luxstrat_' . $captured_lang . '_ran_' . $now->format( 'Y-m-d' ) );
                } catch ( \Exception $e ) {}
            } else {
                error_log( 'LuxStrat[' . $captured_lang . '] catch-up success: Post #' . $result['post_id'] );
            }

            // v2.15.0: Release global lock
            if ( class_exists( 'DBA_Conflict_Center' ) ) DBA_Conflict_Center::release( 'luxstrat_' . $captured_lang );
        } );
    }

    /* ──────────────────────────────────────────────────
     *  MAIN CRON CALLBACK
     * ────────────────────────────────────────────────── */

    public function run( $lang, $lang_config ) {
        $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
        if ( empty( $settings['enabled'] ) ) return;

        try {
            $tz  = new DateTimeZone( self::TIMEZONE );
            $now = new DateTime( 'now', $tz );
        } catch ( \Exception $e ) { $now = new DateTime( 'now' ); }

        $days = ! empty( $lang_config['schedule']['days'] ) ? $lang_config['schedule']['days'] : array();
        if ( ! in_array( (int) $now->format( 'N' ), $days, true ) ) {
            error_log( 'LuxStrat[' . $lang . ']: ' . $now->format( 'l' ) . ' is not a target day. Skipping.' );
            return;
        }

        $lock_key = 'dba_luxstrat_' . $lang . '_ran_' . $now->format( 'Y-m-d' );
        if ( get_transient( $lock_key ) ) return;
        set_transient( $lock_key, true, DAY_IN_SECONDS );

        // v2.15.0: Global conflict lock — prevents overlap with ALL modules
        $hook = 'dba_luxstrat_' . $lang . '_cron';
        if ( class_exists( 'DBA_Conflict_Center' ) && ! DBA_Conflict_Center::acquire( 'luxstrat_' . $lang, $hook ) ) {
            delete_transient( $lock_key ); // Let catch-up retry
            error_log( 'LuxStrat[' . $lang . ']: Another module running — rescheduled +10min' );
            return;
        }

        $result = DBA_LuxStrat_Intl_Generator::run( $lang, $lang_config );

        if ( is_wp_error( $result ) ) {
            error_log( 'LuxStrat[' . $lang . ']: Error — ' . $result->get_error_message() );
            delete_transient( $lock_key );
        } else {
            error_log( 'LuxStrat[' . $lang . ']: Success — Post #' . $result['post_id'] . ' (' . $result['words'] . ' words)' );
        }

        // v2.15.0: Release global lock
        if ( class_exists( 'DBA_Conflict_Center' ) ) DBA_Conflict_Center::release( 'luxstrat_' . $lang );
    }

    /* ──────────────────────────────────────────────────
     *  MANUAL RUN
     * ────────────────────────────────────────────────── */

    public function manual_run( $lang ) {
        $config = isset( $this->languages[ $lang ] ) ? $this->languages[ $lang ] : null;
        if ( ! $config ) {
            return new WP_Error( 'unknown_lang', 'Language "' . $lang . '" not found.' );
        }

        $settings = get_option( 'dba_luxstrat_' . $lang . '_settings', array() );
        if ( empty( $settings['enabled'] ) ) {
            return new WP_Error( 'disabled', 'LuxStrat ' . $lang . ' is disabled.' );
        }

        return DBA_LuxStrat_Intl_Generator::run( $lang, $config, true );
    }

    /* ──────────────────────────────────────────────────
     *  ADMIN INFO
     * ────────────────────────────────────────────────── */

    public static function get_schedule_info( $lang, $lang_config ) {
        $days = ! empty( $lang_config['schedule']['days'] ) ? $lang_config['schedule']['days'] : array();
        $hour = ! empty( $lang_config['schedule']['hour'] ) ? (int) $lang_config['schedule']['hour'] : 7;

        $day_names = array( 1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday' );
        $day_labels = array_map( function( $d ) use ( $day_names ) { return $day_names[ $d ] ?? '?'; }, $days );

        try {
            $tz  = new DateTimeZone( self::TIMEZONE );
            $now = new DateTime( 'now', $tz );
        } catch ( \Exception $e ) {
            return array( 'next_run' => 'Error', 'frequency' => implode( ' & ', $day_labels ) . ' at ' . $hour . ':00', 'cron_active' => false );
        }

        $hook = 'dba_luxstrat_' . $lang . '_cron';
        $ts   = wp_next_scheduled( $hook );

        if ( $ts ) {
            $next_dt = new DateTime( '@' . $ts );
            $next_dt->setTimezone( $tz );
            $next_run = $next_dt->format( 'l, j F Y \a\t H:i' ) . ' (' . self::TIMEZONE . ')';
        } else {
            $next_run = '⚠️ NOT SCHEDULED — visit admin to auto-fix';
        }

        return array(
            'next_run'    => $next_run,
            'timezone'    => self::TIMEZONE,
            'frequency'   => 'Every ' . implode( ' & ', $day_labels ) . ' at ' . sprintf( '%02d:00', $hour ) . ' (self-healing)',
            'cron_active' => (bool) $ts,
            'lock_active' => (bool) get_transient( 'dba_luxstrat_' . $lang . '_ran_' . $now->format( 'Y-m-d' ) ),
        );
    }
}
