<?php
/**
 * The core plugin class.
 *
 * This is used to define admin-specific hooks, public-facing hooks,
 * and load all dependencies.
 *
 * @package Freelance_Studio
 */

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


class Freelance_Studio {

    /**
     * The loader that's responsible for maintaining and registering all hooks.
     *
     * @var FS_Loader
     */
    protected $loader;

    /**
     * The unique identifier of this plugin.
     *
     * @var string
     */
    protected $plugin_name;

    /**
     * The current version of the plugin.
     *
     * @var string
     */
    protected $version;

    /**
     * Define the core functionality of the plugin.
     */
    public function __construct() {
        $this->version = JEEB_FS_VERSION;
        $this->plugin_name = 'freelance-studio';

        $this->load_dependencies();
        $this->define_admin_hooks();
        $this->define_public_hooks();
    }

    /**
     * Load the required dependencies for this plugin.
     */
    private function load_dependencies() {
        // Core classes
        require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-loader.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-admin.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-activator.php';

        // Models - Core (always loaded)
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-client.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-client-contact.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-project.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-proposal.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-proposal-section.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-contract.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-contract-section.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-invoice.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-expense.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-manual-income.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-transaction.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-time-entry.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-task.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-payment-reminder.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-form.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-form-response.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-form-template.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-preset-service.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-external-calendar.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-retainer-period.php';

        // Models - Premium Only (files stripped from free version via @fs_premium_only)
        // These files won't exist in the free version, so check before loading.
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-recurring-invoice.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-recurring-invoice.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-milestone.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-milestone.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-project-template.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-project-template.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-tax-calculator.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-tax-calculator.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-tax-rates.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-tax-rates.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-quarterly-tax.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-quarterly-tax.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-annual-deduction.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-annual-deduction.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-email-template.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-email-template.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-meeting-type.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-meeting-type.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-availability.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-availability.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-booking.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-booking.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-project-file.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/models/class-fs-project-file.php';
        }

        // Template Library
        require_once JEEB_FS_PLUGIN_DIR . 'includes/class-fs-template-library.php';

        // Controllers - Core (always loaded)
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-dashboard-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-clients-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-projects-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-proposals-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-contracts-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-invoices-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-expenses-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-time-tracking-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-settings-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-reporting-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-finances-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-calendar-controller.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-forms-controller.php';

        // Controllers - Premium Only (stripped from free version)
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-recurring-invoices-controller.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-recurring-invoices-controller.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-project-templates-controller.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-project-templates-controller.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-booking-controller.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/controllers/class-fs-booking-controller.php';
        }

        // Services - Core (always loaded)
        require_once JEEB_FS_PLUGIN_DIR . 'includes/libraries/class-fs-pdf.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-pdf-service.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-email-service.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-email-delivery.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-security-service.php';
        
        // Services - Premium Only (stripped from free version)
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-google-calendar-service.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-google-calendar-service.php';
        }
        if ( file_exists( JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-file-handler.php' ) ) {
            require_once JEEB_FS_PLUGIN_DIR . 'includes/services/class-fs-file-handler.php';
        }

        // Initialize security service
        FS_Security_Service::init();

        // Frontend
        require_once JEEB_FS_PLUGIN_DIR . 'includes/frontend/class-fs-frontend-router.php';

        // Helpers
        require_once JEEB_FS_PLUGIN_DIR . 'includes/helpers/class-fs-view-context.php';
        require_once JEEB_FS_PLUGIN_DIR . 'includes/helpers/class-fs-db-helper.php';

        // AJAX handlers
        require_once JEEB_FS_PLUGIN_DIR . 'includes/ajax/class-fs-ajax-handler.php';

        $this->loader = new FS_Loader();
    }

    /**
     * Register all of the hooks related to the admin area.
     */
    private function define_admin_hooks() {
        $plugin_admin = new FS_Admin($this->get_plugin_name(), $this->get_version());

        // Check for database upgrades on admin_init
        $this->loader->add_action('admin_init', $this, 'check_db_upgrades');

        // Admin menu and pages
        $this->loader->add_action('admin_menu', $plugin_admin, 'add_admin_menu');
        
        // Admin styles and scripts
        $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles');
        $this->loader->add_action('admin_head', $plugin_admin, 'render_pwa_meta');
        $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts');
        
        // High contrast mode body class
        $this->loader->add_filter('admin_body_class', $plugin_admin, 'add_body_classes');
        
        // Bug report footer on plugin pages
        $this->loader->add_action('admin_footer', $plugin_admin, 'render_bug_report_footer');
        $this->loader->add_action('admin_footer', $plugin_admin, 'render_floating_timer');

        // AJAX handlers
        $ajax_handler = new FS_Ajax_Handler();
        $this->loader->add_action('wp_ajax_fs_get_client', $ajax_handler, 'get_client');
        $this->loader->add_action('wp_ajax_fs_delete_client', $ajax_handler, 'delete_client');
        $this->loader->add_action('wp_ajax_fs_delete_project', $ajax_handler, 'delete_project');
        $this->loader->add_action('wp_ajax_fs_bulk_archive_projects', $ajax_handler, 'bulk_archive_projects');
        $this->loader->add_action('wp_ajax_fs_delete_invoice', $ajax_handler, 'delete_invoice');
        $this->loader->add_action('wp_ajax_fs_delete_expense', $ajax_handler, 'delete_expense');
        $this->loader->add_action('wp_ajax_fs_delete_annual_deduction', $ajax_handler, 'delete_annual_deduction');
        $this->loader->add_action('wp_ajax_fs_generate_invoice_from_project', $ajax_handler, 'generate_invoice_from_project');
        $this->loader->add_action('wp_ajax_fs_upload_receipt', $ajax_handler, 'upload_receipt');
        $this->loader->add_action('wp_ajax_fs_remove_receipt', $ajax_handler, 'remove_receipt');
        $this->loader->add_action('wp_ajax_fs_get_client_projects', $ajax_handler, 'get_client_projects');
        $this->loader->add_action('wp_ajax_fs_save_contact', $ajax_handler, 'save_contact');
        $this->loader->add_action('wp_ajax_fs_get_contact', $ajax_handler, 'get_contact');
        $this->loader->add_action('wp_ajax_fs_delete_contact', $ajax_handler, 'delete_contact');
        $this->loader->add_action('wp_ajax_fs_update_invoice_status', $ajax_handler, 'update_invoice_status');
        $this->loader->add_action('wp_ajax_fs_delete_proposal', $ajax_handler, 'delete_proposal');
        $this->loader->add_action('wp_ajax_fs_save_proposal_section_template', $ajax_handler, 'save_proposal_section_template');
        $this->loader->add_action('wp_ajax_fs_delete_proposal_section_template', $ajax_handler, 'delete_proposal_section_template');
        $this->loader->add_action('wp_ajax_fs_get_proposal_section_templates', $ajax_handler, 'get_proposal_section_templates');
        $this->loader->add_action('wp_ajax_fs_delete_contract', $ajax_handler, 'delete_contract');
        $this->loader->add_action('wp_ajax_fs_delete_form', $ajax_handler, 'delete_form');
        $this->loader->add_action('wp_ajax_fs_delete_form_response', $ajax_handler, 'delete_form_response');
        $this->loader->add_action('wp_ajax_fs_get_client_contacts', $ajax_handler, 'get_client_contacts');
        $this->loader->add_action('wp_ajax_fs_get_email_subject', $ajax_handler, 'get_email_subject');
        $this->loader->add_action('wp_ajax_fs_get_client_contacts_for_send', $ajax_handler, 'get_client_contacts_for_send');
        $this->loader->add_action('wp_ajax_fs_send_invoice_with_message', $ajax_handler, 'send_invoice_with_message');
        $this->loader->add_action('wp_ajax_fs_send_payment_receipt', $ajax_handler, 'send_payment_receipt');
        $this->loader->add_action('wp_ajax_fs_send_proposal_with_message', $ajax_handler, 'send_proposal_with_message');
        $this->loader->add_action('wp_ajax_fs_send_contract_with_message', $ajax_handler, 'send_contract_with_message');
        
        // Time tracking AJAX actions
        $this->loader->add_action('wp_ajax_fs_start_timer', $ajax_handler, 'start_timer');
        $this->loader->add_action('wp_ajax_fs_stop_timer', $ajax_handler, 'stop_timer');
        $this->loader->add_action('wp_ajax_fs_pause_timer', $ajax_handler, 'pause_timer');
        $this->loader->add_action('wp_ajax_fs_resume_timer', $ajax_handler, 'resume_timer');
        $this->loader->add_action('wp_ajax_fs_bulk_time_action', $ajax_handler, 'bulk_time_action');
        $this->loader->add_action('wp_ajax_fs_save_widget_order', $ajax_handler, 'save_widget_order');
        $this->loader->add_action('wp_ajax_fs_reset_widget_order', $ajax_handler, 'reset_widget_order');
        $this->loader->add_action('wp_ajax_fs_save_widget_layout', $ajax_handler, 'save_widget_layout');
        $this->loader->add_action('wp_ajax_fs_reset_widget_layout', $ajax_handler, 'reset_widget_layout');
        $this->loader->add_action('wp_ajax_fs_save_task_widget_filter', $ajax_handler, 'save_task_widget_filter');
        $this->loader->add_action('wp_ajax_fs_save_task_widget_limit', $ajax_handler, 'save_task_widget_limit');
        $this->loader->add_action('wp_ajax_fs_save_task_sort_preference', $ajax_handler, 'save_task_sort_preference');
        $this->loader->add_action('wp_ajax_fs_toggle_week_external_events', $ajax_handler, 'toggle_week_external_events');
        $this->loader->add_action('wp_ajax_fs_get_time_entry', $ajax_handler, 'get_time_entry');
        $this->loader->add_action('wp_ajax_fs_save_time_entry', $ajax_handler, 'save_time_entry');
        $this->loader->add_action('wp_ajax_fs_delete_time_entry', $ajax_handler, 'delete_time_entry');
        $this->loader->add_action('wp_ajax_fs_quick_add_time', $ajax_handler, 'quick_add_time');
        $this->loader->add_action('wp_ajax_fs_generate_invoice_from_time', $ajax_handler, 'generate_invoice_from_time');
        $this->loader->add_action('wp_ajax_fs_get_unbilled_time', $ajax_handler, 'get_unbilled_time');
        $this->loader->add_action('wp_ajax_fs_duplicate_time_entry', $ajax_handler, 'duplicate_time_entry');
        $this->loader->add_action('wp_ajax_fs_toggle_time_billable', $ajax_handler, 'toggle_time_billable');
        
        // Recurring invoice AJAX actions
        $this->loader->add_action('wp_ajax_fs_delete_recurring_invoice', $ajax_handler, 'delete_recurring_invoice');
        $this->loader->add_action('wp_ajax_fs_update_recurring_status', $ajax_handler, 'update_recurring_status');
        
        // Booking AJAX actions
        $this->loader->add_action('wp_ajax_fs_cancel_booking', $ajax_handler, 'cancel_booking');
        
        // Milestone and Task AJAX actions
        $this->loader->add_action('wp_ajax_fs_get_milestone', $ajax_handler, 'get_milestone');
        $this->loader->add_action('wp_ajax_fs_save_milestone', $ajax_handler, 'save_milestone');
        $this->loader->add_action('wp_ajax_fs_delete_milestone', $ajax_handler, 'delete_milestone');
        $this->loader->add_action('wp_ajax_fs_complete_milestone', $ajax_handler, 'complete_milestone');
        $this->loader->add_action('wp_ajax_fs_get_task', $ajax_handler, 'get_task');
        $this->loader->add_action('wp_ajax_fs_get_project_tasks', $ajax_handler, 'get_project_tasks');
        $this->loader->add_action('wp_ajax_fs_save_task', $ajax_handler, 'save_task');
        $this->loader->add_action('wp_ajax_fs_delete_task', $ajax_handler, 'delete_task');
        $this->loader->add_action('wp_ajax_fs_archive_task', $ajax_handler, 'archive_task');
        $this->loader->add_action('wp_ajax_fs_restore_task', $ajax_handler, 'restore_task');
        $this->loader->add_action('wp_ajax_fs_archive_completed_tasks', $ajax_handler, 'archive_completed_tasks');
        $this->loader->add_action('wp_ajax_fs_purge_archived_tasks', $ajax_handler, 'purge_archived_tasks');
        $this->loader->add_action('wp_ajax_fs_submit_feature_request', $ajax_handler, 'submit_feature_request');
        $this->loader->add_action('wp_ajax_fs_toggle_task', $ajax_handler, 'toggle_task');
        $this->loader->add_action('wp_ajax_fs_update_task_status', $ajax_handler, 'update_task_status');
        $this->loader->add_action('wp_ajax_fs_save_kanban_task', $ajax_handler, 'save_task');
        $this->loader->add_action('wp_ajax_fs_save_kanban_column_label', $ajax_handler, 'save_kanban_column_label');
        
        // Project template AJAX actions
        $this->loader->add_action('wp_ajax_fs_delete_project_template', $ajax_handler, 'delete_project_template');
        $this->loader->add_action('wp_ajax_fs_apply_project_template', $ajax_handler, 'apply_project_template');
        $this->loader->add_action('wp_ajax_fs_import_library_template', $ajax_handler, 'import_library_template');
        
        // Project services and billing AJAX actions
        $this->loader->add_action('wp_ajax_fs_update_project_services', $ajax_handler, 'update_project_services');
        $this->loader->add_action('wp_ajax_fs_update_project_billing_schedule', $ajax_handler, 'update_project_billing_schedule');
        $this->loader->add_action('wp_ajax_fs_update_project_deposit', $ajax_handler, 'update_project_deposit');
        $this->loader->add_action('wp_ajax_fs_update_project_notes', $ajax_handler, 'update_project_notes');
        
        // Invoice wizard AJAX actions (v3.2.0)
        $this->loader->add_action('wp_ajax_fs_get_invoice_wizard_data', $ajax_handler, 'get_invoice_wizard_data');
        $this->loader->add_action('wp_ajax_fs_get_unbilled_time_entries', $ajax_handler, 'get_unbilled_time_entries');
        $this->loader->add_action('wp_ajax_fs_preview_invoice_items', $ajax_handler, 'preview_invoice_items');
        $this->loader->add_action('wp_ajax_fs_fix_orphaned_time_entries', $ajax_handler, 'fix_orphaned_time_entries');
        $this->loader->add_action('wp_ajax_fs_create_invoice_from_wizard', $ajax_handler, 'create_invoice_from_wizard');
        $this->loader->add_action('wp_ajax_fs_check_invoice_editable', $ajax_handler, 'check_invoice_editable');
        
        // Payment reminder AJAX actions
        $this->loader->add_action('wp_ajax_fs_send_reminder', $ajax_handler, 'send_reminder');
        $this->loader->add_action('wp_ajax_fs_schedule_reminders', $ajax_handler, 'schedule_reminders');
        
        // Invoice expense billback
        $this->loader->add_action('wp_ajax_fs_get_project_expenses', $ajax_handler, 'get_project_expenses');
        
        // Dashboard quick actions
        $this->loader->add_action('wp_ajax_fs_quick_add_task', $ajax_handler, 'quick_add_task');
        $this->loader->add_action('wp_ajax_fs_quick_start_timer', $ajax_handler, 'quick_start_timer');
        $this->loader->add_action('wp_ajax_fs_get_mini_calendar', $ajax_handler, 'get_mini_calendar');
        
        // Public AJAX actions (for client signing)
        $this->loader->add_action('wp_ajax_fs_sign_contract', $ajax_handler, 'sign_contract');
        $this->loader->add_action('wp_ajax_nopriv_fs_sign_contract', $ajax_handler, 'sign_contract');
        
        // Client portal task toggle (public)
        $this->loader->add_action('wp_ajax_fs_client_toggle_task', $ajax_handler, 'client_toggle_task');
        $this->loader->add_action('wp_ajax_nopriv_fs_client_toggle_task', $ajax_handler, 'client_toggle_task');
        
        // Client portal task request (public)
        $this->loader->add_action('wp_ajax_fs_client_add_task', $ajax_handler, 'client_add_task');
        $this->loader->add_action('wp_ajax_nopriv_fs_client_add_task', $ajax_handler, 'client_add_task');
        
        // Accept task request (admin)
        $this->loader->add_action('wp_ajax_fs_accept_task_request', $ajax_handler, 'accept_task_request');
        
        // Cron job for payment reminders
        $this->loader->add_action('fs_send_payment_reminders', $this, 'process_payment_reminders');
        
        // Cron job for quarterly tax reminders
        $this->loader->add_action('fs_quarterly_tax_reminder_check', $this, 'process_quarterly_tax_reminders');
        
        // Cron job for weekly digest email
        $this->loader->add_action('fs_weekly_digest_email', $this, 'send_weekly_digest');
        
        // Cron job for recurring expense generation
        $this->loader->add_action('fs_generate_recurring_expenses', $this, 'process_recurring_expenses');
        
        // Cron job for recurring task generation
        $this->loader->add_action('fs_generate_recurring_tasks', $this, 'process_recurring_tasks');
        
        // Cron job for external calendar sync
        $this->loader->add_action('fs_sync_external_calendars', $this, 'process_external_calendar_sync');
        
        // Initialize Google Calendar Service (registers its own hooks)
        FS_Google_Calendar_Service::get_instance();
        FS_Google_Calendar_Service::get_instance();
        
        // Google Calendar AJAX handlers
        $this->loader->add_action('wp_ajax_fs_gcal_full_sync', $ajax_handler, 'gcal_full_sync');
        $this->loader->add_action('wp_ajax_fs_gcal_inbound_sync', $ajax_handler, 'gcal_inbound_sync');
        $this->loader->add_action('wp_ajax_fs_gcal_preview_digest', $ajax_handler, 'gcal_preview_digest');
        $this->loader->add_action('wp_ajax_fs_gcal_send_test_digest', $ajax_handler, 'gcal_send_test_digest');
        $this->loader->add_action('wp_ajax_fs_gcal_create_event', $ajax_handler, 'gcal_create_event');
        $this->loader->add_action('wp_ajax_fs_gcal_update_event', $ajax_handler, 'gcal_update_event');
        $this->loader->add_action('wp_ajax_fs_gcal_delete_event', $ajax_handler, 'gcal_delete_event');
        $this->loader->add_action('wp_ajax_fs_gcal_get_events', $ajax_handler, 'gcal_get_events');
        
        // Settings auto-save
        $this->loader->add_action('wp_ajax_fs_auto_save_settings', $ajax_handler, 'auto_save_settings');
        
        // Preset services
        $this->loader->add_action('wp_ajax_fs_get_preset_services', $ajax_handler, 'get_preset_services');
        $this->loader->add_action('wp_ajax_fs_save_preset_service', $ajax_handler, 'save_preset_service');
        $this->loader->add_action('wp_ajax_fs_delete_preset_service', $ajax_handler, 'delete_preset_service');
        
        // Quarterly tax payments
        $this->loader->add_action('wp_ajax_fs_update_quarterly_payment', $ajax_handler, 'update_quarterly_payment');
        
        // v3.8.0 - New features
        $this->loader->add_action('wp_ajax_fs_duplicate_project', $ajax_handler, 'duplicate_project');
        $this->loader->add_action('wp_ajax_fs_batch_mark_invoices_paid', $ajax_handler, 'batch_mark_invoices_paid');
        $this->loader->add_action('wp_ajax_fs_export_csv', $ajax_handler, 'export_csv');
        $this->loader->add_action('wp_ajax_fs_generate_1099_pdf', $ajax_handler, 'generate_1099_pdf');
        
        // v5.0.15 - Project Files
        $this->loader->add_action('wp_ajax_fs_upload_project_file', $ajax_handler, 'upload_project_file');
        $this->loader->add_action('wp_ajax_fs_delete_project_file', $ajax_handler, 'delete_project_file');
        $this->loader->add_action('wp_ajax_fs_update_project_file', $ajax_handler, 'update_project_file');
        $this->loader->add_action('wp_ajax_fs_get_project_files', $ajax_handler, 'get_project_files');
        $this->loader->add_action('wp_ajax_fs_generate_upload_link', $ajax_handler, 'generate_upload_link');
        $this->loader->add_action('wp_ajax_fs_client_upload_file', $ajax_handler, 'client_upload_file');
        $this->loader->add_action('wp_ajax_nopriv_fs_client_upload_file', $ajax_handler, 'client_upload_file');
        $this->loader->add_action('wp_ajax_fs_portal_upload_file', $ajax_handler, 'portal_upload_file');
        $this->loader->add_action('wp_ajax_nopriv_fs_portal_upload_file', $ajax_handler, 'portal_upload_file');
        
        // Data export (admin only)
        $this->loader->add_action('wp_ajax_fs_export_all_data', $ajax_handler, 'export_all_data');
        $this->loader->add_action('wp_ajax_fs_export_csv_zip', $ajax_handler, 'export_csv_zip');

        // Retainer period manual reset
        $this->loader->add_action('wp_ajax_fs_reset_retainer_period', $ajax_handler, 'reset_retainer_period');
        
        // Bug report
        $this->loader->add_action('wp_ajax_fs_submit_bug_report', $plugin_admin, 'handle_bug_report');

        // Import
        $this->loader->add_action('wp_ajax_fs_import_preview', $ajax_handler, 'import_preview');
        $this->loader->add_action('wp_ajax_fs_import_batch', $ajax_handler, 'import_batch');
        $this->loader->add_action('wp_ajax_fs_import_expenses_batch', $ajax_handler, 'import_expenses_batch');
        $this->loader->add_action('wp_ajax_fs_check_transaction_duplicates', $ajax_handler, 'check_transaction_duplicates');
        $this->loader->add_action('wp_ajax_fs_import_transactions_batch', $ajax_handler, 'import_transactions_batch');
        $this->loader->add_action('wp_ajax_fs_get_transaction_details', $ajax_handler, 'get_transaction_details');
        $this->loader->add_action('wp_ajax_fs_match_transaction', $ajax_handler, 'match_transaction');
        $this->loader->add_action('wp_ajax_fs_unmatch_transaction', $ajax_handler, 'unmatch_transaction');
        $this->loader->add_action('wp_ajax_fs_toggle_transaction_ignore', $ajax_handler, 'toggle_transaction_ignore');
        $this->loader->add_action('wp_ajax_fs_create_expense_from_transaction', $ajax_handler, 'create_expense_from_transaction');
        $this->loader->add_action('wp_ajax_fs_save_transaction_category', $ajax_handler, 'save_transaction_category');
        $this->loader->add_action('wp_ajax_fs_get_personal_rules', $ajax_handler, 'get_personal_rules');
        $this->loader->add_action('wp_ajax_fs_save_personal_rules', $ajax_handler, 'save_personal_rules');
        $this->loader->add_action('wp_ajax_fs_run_personal_rules', $ajax_handler, 'run_personal_rules');

        // Merge clients
        $this->loader->add_action('wp_ajax_fs_merge_client_preview', $ajax_handler, 'merge_client_preview');
        $this->loader->add_action('wp_ajax_fs_merge_client', $ajax_handler, 'merge_client');

        // Email Delivery (v8.6.0)
        $this->loader->add_action('wp_ajax_fs_email_delivery_verify_key', $ajax_handler, 'email_delivery_verify_key');
        $this->loader->add_action('wp_ajax_fs_email_delivery_send_test', $ajax_handler, 'email_delivery_send_test');
        $this->loader->add_action('wp_ajax_fs_email_delivery_complete_setup', $ajax_handler, 'email_delivery_complete_setup');
    }

    /**
     * Register all of the hooks related to the public-facing functionality.
     */
    private function define_public_hooks() {
        // Initialize frontend router for public document viewing
        FS_Frontend_Router::init();

        // Initialize email delivery routing (v8.6.0)
        FS_Email_Delivery::init();

        // Register the changelog shortcode (renders on freelancestud.io's /changelog/ page)
        $this->loader->add_action( 'init', $this, 'register_changelog_shortcode' );
        
        // Schedule daily cron job for contract expiration
        $this->loader->add_action('init', $this, 'schedule_cron_jobs');
        $this->loader->add_action('fs_daily_tasks', $this, 'run_daily_tasks');
    }

    /**
     * Register the [fs_changelog] shortcode AND a the_content filter fallback.
     * The filter catches cases where Thrive Architect bypasses do_shortcode().
     * Only active on freelancestud.io.
     */
    public function register_changelog_shortcode() {
        if ( ! self::is_freelancestudio_site() ) {
            return;
        }
        add_shortcode( 'fs_changelog', array( $this, 'render_changelog_shortcode' ) );
        add_filter( 'the_content', array( $this, 'inject_changelog_content' ), 20 );
    }

    /**
     * Shortcode callback for [fs_changelog].
     */
    public function render_changelog_shortcode( $atts = array(), $content = '' ) {
        return $this->get_changelog_html();
    }

    /**
     * Filter: replace page content with changelog output when on the changelog page.
     * This fires even when Thrive Architect intercepts do_shortcode.
     */
    public function inject_changelog_content( $content ) {
        if ( ! is_page( 'changelog' ) ) {
            return $content;
        }
        return $this->get_changelog_html();
    }

    /**
     * Build and return the changelog HTML output.
     */
    private function get_changelog_html() {
        $versions = FS_Frontend_Router::parse_changelog();
        $GLOBALS['fs_changelog_data'] = $versions;
        ob_start();
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/changelog.php';
        return ob_get_clean();
    }

    /**
     * Helper: is this the freelancestud.io site?
     */
    private static function is_freelancestudio_site() {
        $host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : '';
        $host = preg_replace( '/:\d+$/', '', $host );
        return in_array( $host, array( 'freelancestud.io', 'www.freelancestud.io' ), true );
    }

    /**
     * Schedule cron jobs
     */
    public function schedule_cron_jobs() {
        if (!wp_next_scheduled('fs_daily_tasks')) {
            wp_schedule_event(time(), 'daily', 'fs_daily_tasks');
        }
    }

    /**
     * Run daily scheduled tasks
     */
    public function run_daily_tasks() {
        // Auto-expire contracts past valid_until date
        $this->expire_contracts();
        
        // Process recurring invoices
        $this->process_recurring_invoices();

        // Generate new retainer periods at the start of each billing cycle
        $this->generate_retainer_periods();

        // Auto-archive completed tasks if enabled
        $this->auto_archive_completed_tasks();
    }

    /**
     * Generate new retainer periods for active retainer projects whose billing cycle has started
     */
    private function generate_retainer_periods() {
        global $wpdb;
        $projects_table = $wpdb->prefix . 'fs_projects';
        $periods_table  = $wpdb->prefix . 'fs_retainer_periods';

        // Check if retainer_periods table exists
        if ( ! $wpdb->get_var( "SHOW TABLES LIKE '{$periods_table}'" ) ) {
            return;
        }

        $today = current_time( 'Y-m-d' );

        // Get all active retainer projects
        $retainer_projects = $wpdb->get_results(
            "SELECT * FROM {$projects_table} WHERE is_retainer = 1 AND status = 'active'"
        );

        if ( empty( $retainer_projects ) ) {
            return;
        }

        foreach ( $retainer_projects as $project ) {
            // Skip if no retainer amount or hours configured
            if ( empty( $project->retainer_amount ) || empty( $project->retainer_hours ) ) {
                continue;
            }

            // Check if a period already exists covering today
            $existing = $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$periods_table}
                 WHERE project_id = %d AND period_start <= %s AND period_end >= %s LIMIT 1",
                $project->id, $today, $today
            ) );

            if ( $existing ) {
                continue; // Period already covers today — nothing to do
            }

            // Determine reset day: explicit setting takes priority, then start_date, then 1st
            $reference_day = 1; // Default: 1st of month
            if ( ! empty( $project->retainer_reset_day ) ) {
                $reference_day = intval( $project->retainer_reset_day );
            } elseif ( ! empty( $project->billing_start_date ) ) {
                $reference_day = intval( gmdate( 'j', strtotime( $project->billing_start_date ) ) );
            } elseif ( ! empty( $project->start_date ) ) {
                $reference_day = intval( gmdate( 'j', strtotime( $project->start_date ) ) );
            }
            // Clamp to 1–28 to be safe
            $reference_day = max( 1, min( 28, $reference_day ) );

            // Calculate period_start: this month's reference_day (or last month's if today < reference_day)
            $current_year  = intval( gmdate( 'Y' ) );
            $current_month = intval( gmdate( 'n' ) );
            $today_day     = intval( gmdate( 'j' ) );

            if ( $today_day >= $reference_day ) {
                $period_start = gmdate( 'Y-m-d', mktime( 0, 0, 0, $current_month, $reference_day, $current_year ) );
            } else {
                // Billing started earlier last month
                $prev_month   = $current_month - 1;
                $prev_year    = $current_year;
                if ( $prev_month < 1 ) { $prev_month = 12; $prev_year--; }
                $period_start = gmdate( 'Y-m-d', mktime( 0, 0, 0, $prev_month, $reference_day, $prev_year ) );
            }

            // period_end: one day before the next reference_day
            $period_start_ts = strtotime( $period_start );
            $period_end_ts   = strtotime( '+1 month', $period_start_ts ) - 86400;
            $period_end      = gmdate( 'Y-m-d', $period_end_ts );

            // Double-check this period doesn't already exist (different reference_day edge cases)
            $dup = $wpdb->get_var( $wpdb->prepare(
                "SELECT id FROM {$periods_table}
                 WHERE project_id = %d AND period_start = %s LIMIT 1",
                $project->id, $period_start
            ) );

            if ( $dup ) {
                continue;
            }

            FS_Retainer_Period::generate_period( $project->id, $period_start, $period_end );
            error_log( "Freelance Studio: Generated retainer period {$period_start} – {$period_end} for project {$project->id}" );
        }
    }

    /**
     * Process recurring invoices that are due
     */
    private function process_recurring_invoices() {
        $generated = FS_Recurring_Invoice::process_due_invoices();
        
        if (!empty($generated)) {
            error_log('Freelance Studio: Generated ' . count($generated) . ' recurring invoices');
        }
    }

    /**
     * Expire contracts that are past their valid_until date
     */
    private function expire_contracts() {
        global $wpdb;
        $table = $wpdb->prefix . 'fs_contracts';
        
        // Find contracts that should be expired
        // Only expire contracts that are in draft, sent, or viewed status
        $expirable_statuses = array('draft', 'sent', 'viewed');
        $statuses_in = "'" . implode("','", $expirable_statuses) . "'";
        
        $today = current_time('Y-m-d');
        
        $wpdb->query($wpdb->prepare(
            "UPDATE {$table} 
             SET status = 'expired', updated_at = %s 
             WHERE valid_until IS NOT NULL 
             AND valid_until < %s 
             AND status IN ({$statuses_in})",
            current_time('mysql'),
            $today
        ));
    }

    /**
     * Run the loader to execute all of the hooks with WordPress.
     */
    public function run() {
        $this->loader->run();
    }

    /**
     * The name of the plugin used to uniquely identify it.
     *
     * @return string The name of the plugin.
     */
    public function get_plugin_name() {
        return $this->plugin_name;
    }

    /**
     * The reference to the class that orchestrates the hooks.
     *
     * @return FS_Loader Orchestrates the hooks of the plugin.
     */
    public function get_loader() {
        return $this->loader;
    }

    /**
     * Retrieve the version number of the plugin.
     *
     * @return string The version number of the plugin.
     */
    public function get_version() {
        return $this->version;
    }

    /**
     * Check for and run database upgrades
     */
    public function check_db_upgrades() {
        $db_version = get_option('fs_db_version', '1.0.0');
        
        // If DB version is less than current version, run upgrades
        if (version_compare($db_version, JEEB_FS_VERSION, '<')) {
            FS_Activator::run_upgrades();
        }
        
        // Always verify schema once per version to catch any missing columns
        // This handles cases where plugin was updated via FTP without reactivation
        $schema_version = get_option('fs_schema_version', '0');
        if (version_compare($schema_version, JEEB_FS_VERSION, '<')) {
            FS_Activator::verify_database_schema();
            update_option('fs_schema_version', JEEB_FS_VERSION);
        }
        
        // Monitor permalink structure changes and flush rewrite rules if changed
        // This ensures booking URLs work after switching permalink formats
        $current_permalink = get_option('permalink_structure', '');
        $stored_permalink = get_option('fs_permalink_structure', null);
        
        if ($stored_permalink !== $current_permalink) {
            // Permalink structure changed, flush rewrite rules
            flush_rewrite_rules();
            update_option('fs_permalink_structure', $current_permalink);
        }
    }

    /**
     * Auto-archive completed tasks based on the user's setting (called by daily cron)
     */
    private function auto_archive_completed_tasks() {
        $days = (int) get_option( 'fs_auto_archive_completed_tasks', 0 );
        if ( $days <= 0 ) {
            return;
        }

        global $wpdb;
        $table     = $wpdb->prefix . 'fs_tasks';
        $threshold = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );

        $wpdb->query( $wpdb->prepare(
            "UPDATE {$table}
             SET previous_status = status, status = 'archived'
             WHERE status = 'completed'
               AND ( updated_at <= %s OR ( updated_at IS NULL AND created_at <= %s ) )",
            $threshold,
            $threshold
        ) );
    }

    /**
     * Process payment reminders (called by cron)
     */
    public function process_payment_reminders() {
        // Check if reminders are enabled
        if (!get_option('fs_reminder_enabled', '1')) {
            return;
        }

        $due_reminders = FS_Payment_Reminder::get_due_reminders();
        
        foreach ($due_reminders as $reminder) {
            $this->send_reminder_email($reminder);
        }
    }

    /**
     * Send a reminder email
     */
    private function send_reminder_email($reminder) {
        $invoice = FS_Invoice::get($reminder->invoice_id);
        if (!$invoice) {
            FS_Payment_Reminder::update_status($reminder->id, 'skipped');
            return;
        }

        $client = FS_Client::get($invoice->client_id);
        if (!$client) {
            FS_Payment_Reminder::update_status($reminder->id, 'skipped');
            return;
        }

        // Resolve recipients from saved send_to_contacts (same contacts the invoice was sent to)
        $to_info = null;
        $cc_emails = array();
        
        if (!empty($invoice->send_to_contacts)) {
            $saved = json_decode($invoice->send_to_contacts, true);
            
            if ($saved && isset($saved['to'])) {
                // New format: {"to": "primary"|contact_id, "cc": [...]}
                $to_info = FS_Email_Service::resolve_contact_info($client, $saved['to']);
                if (!empty($saved['cc'])) {
                    foreach ($saved['cc'] as $cc_id) {
                        $cc_info = FS_Email_Service::resolve_contact_info($client, $cc_id);
                        if ($cc_info && (!$to_info || $cc_info['email'] !== $to_info['email'])) {
                            $cc_emails[] = $cc_info['email'];
                        }
                    }
                }
            } elseif (is_array($saved)) {
                // Legacy format: plain array of contact IDs
                foreach ($saved as $contact_id) {
                    $info = FS_Email_Service::resolve_contact_info($client, $contact_id);
                    if ($info) {
                        if (!$to_info) {
                            $to_info = $info;
                        } elseif ($info['email'] !== $to_info['email']) {
                            $cc_emails[] = $info['email'];
                        }
                    }
                }
            }
        }
        
        // Fall back to primary client email
        if (!$to_info) {
            $to_info = FS_Email_Service::resolve_contact_info($client, 'primary');
        }
        
        if (!$to_info || empty($to_info['email'])) {
            FS_Payment_Reminder::update_status($reminder->id, 'skipped');
            return;
        }

        // Generate view token if not exists
        if (empty($invoice->view_token)) {
            global $wpdb;
            $view_token = wp_generate_password(32, false);
            $wpdb->update(
                $wpdb->prefix . 'fs_invoices',
                array('view_token' => $view_token),
                array('id' => $invoice->id)
            );
            $invoice->view_token = $view_token;
        }

        // Determine subject
        $is_overdue = !empty($reminder->reminder_type) && strpos($reminder->reminder_type, 'overdue') !== false;
        $subject_template = $is_overdue 
            ? get_option('fs_reminder_subject_overdue', 'Invoice {invoice_number} is overdue')
            : get_option('fs_reminder_subject_before', 'Friendly reminder: Invoice {invoice_number} due soon');

        // Replace placeholders
        $subject = str_replace(
            array('{invoice_number}', '{client_name}', '{amount}', '{due_date}'),
            array(
                $invoice->invoice_number,
                $client->company_name ?: $client->name,
                FS_View_Context::format_currency($invoice->balance_due),
                gmdate('F j, Y', strtotime($invoice->due_date))
            ),
            $subject_template
        );

        // Generate invoice URL
        $invoice_url = home_url('?fs_invoice=' . $invoice->view_token);
        
        $settings = array(
            'business_name' => get_option('fs_business_name', ''),
            'business_email' => get_option('fs_business_email', ''),
        );

        // Build email content
        $content = '';
        if ($is_overdue) {
            /* translators: 1. recipient name */
            $content .= '<p style="margin: 0 0 24px 0;">' . sprintf(__('This is a reminder that Invoice %s is past due.', 'freelance-studio'), '<strong>' . esc_html($invoice->invoice_number) . '</strong>') . '</p>';
        } else {
            /* translators: 1. recipient name */
            $content .= '<p style="margin: 0 0 24px 0;">' . sprintf(__('This is a friendly reminder that Invoice %s will be due soon.', 'freelance-studio'), '<strong>' . esc_html($invoice->invoice_number) . '</strong>') . '</p>';
        }
        
        // Invoice details box
        $box_bg = $is_overdue ? '#fef2f2' : '#f9fafb';
        $box_color = $is_overdue ? '#991b1b' : '#111827';
        $content .= '<table width="100%" cellpadding="0" cellspacing="0" style="background-color: ' . $box_bg . '; border-radius: 8px; margin-bottom: 24px;">';
        $content .= '<tr><td style="padding: 24px;">';
        $content .= '<p style="margin: 0 0 8px 0; color: ' . $box_color . ';"><strong>' . __('Amount Due:', 'freelance-studio') . '</strong> ' . FS_View_Context::format_currency($invoice->balance_due) . '</p>';
        $content .= '<p style="margin: 0; color: ' . $box_color . ';"><strong>' . __('Due Date:', 'freelance-studio') . '</strong> ' . gmdate('F j, Y', strtotime($invoice->due_date)) . '</p>';
        $content .= '</td></tr></table>';
        
        if ($is_overdue) {
            $content .= '<p style="margin: 0 0 24px 0; color: #991b1b;">' . __('Please submit payment at your earliest convenience.', 'freelance-studio') . '</p>';
        }

        // Add custom message if set
        if (!empty($reminder->custom_message)) {
            $content .= '<p style="margin: 0 0 24px 0;">' . nl2br(esc_html($reminder->custom_message)) . '</p>';
        }

        $title = $is_overdue ? __('Payment Overdue', 'freelance-studio') : __('Payment Reminder', 'freelance-studio');
        
        // Build headers with CC recipients
        $send_options = array(
            'cta_text' => __('View Invoice', 'freelance-studio'),
            'cta_url' => $invoice_url,
        );
        if (!empty($cc_emails)) {
            $send_options['cc'] = $cc_emails;
        }
        
        $sent = FS_Email_Service::send_styled_email(
            $to_info['email'],
            $subject,
            $title,
            $content,
            $send_options
        );

        if ($sent) {
            FS_Email_Service::log_email($to_info['email'], $subject, 'payment_reminder', $invoice->id);
            FS_Payment_Reminder::mark_sent($reminder->id);
        }
    }

    /**
     * Process quarterly tax reminders
     */
    public function process_quarterly_tax_reminders() {
        // Check if reminders are enabled
        if (!get_option('fs_quarterly_reminder_enabled', 0)) {
            return;
        }
        
        $reminder_days = intval(get_option('fs_quarterly_reminder_days', 7));
        $reminder_email = get_option('fs_quarterly_reminder_email', get_option('fs_business_email', get_option('admin_email')));
        
        if (empty($reminder_email)) {
            return;
        }
        
        $current_year = gmdate('Y');
        $current_date = current_time('Y-m-d');
        
        // Define quarterly deadlines
        $quarters = array(
            'Q1' => array(
                'deadline' => $current_year . '-04-15',
                'label' => 'Q1 (January - March)',
            ),
            'Q2' => array(
                'deadline' => $current_year . '-06-15',
                'label' => 'Q2 (April - May)',
            ),
            'Q3' => array(
                'deadline' => $current_year . '-09-15',
                'label' => 'Q3 (June - August)',
            ),
            'Q4' => array(
                'deadline' => ($current_year + 1) . '-01-15',
                'label' => 'Q4 (September - December)',
            ),
        );
        
        // Get payments already made
        $payments = FS_Tax_Calculator::get_quarterly_payments_made($current_year);
        
        // Check each quarter
        foreach ($quarters as $quarter_key => $quarter_info) {
            $deadline = $quarter_info['deadline'];
            $deadline_date = strtotime($deadline);
            $days_until = floor(($deadline_date - strtotime($current_date)) / 86400);
            
            // Check if we should send reminder
            if ($days_until === $reminder_days) {
                // Check if already paid for this quarter
                if ($payments['by_quarter'][$quarter_key] > 0) {
                    continue; // Already paid, skip
                }
                
                // Check if we already sent a reminder for this quarter/deadline
                $sent_key = 'fs_quarterly_reminder_sent_' . $quarter_key . '_' . $current_year;
                if (get_option($sent_key, false)) {
                    continue; // Already sent
                }
                
                // Calculate recommended amount
                $income = FS_Tax_Calculator::get_ytd_income($current_year);
                $expenses = FS_Tax_Calculator::get_ytd_expenses($current_year);
                $annual_deductions = FS_Annual_Deduction::get_total($current_year);
                $state = get_option('fs_tax_state', '');
                $federal_bracket = get_option('fs_federal_tax_bracket', 22);
                
                $tax_calculation = FS_Tax_Calculator::calculate_tax_liability(
                    $income, $state, $federal_bracket, $expenses, $annual_deductions
                );
                
                $recommended = $tax_calculation['quarterly']['recommended_quarterly'];
                
                // Get email template
                $template = FS_Email_Template::get('quarterly_tax_reminder');
                
                // Build variables
                $vars = array(
                    '{quarter}' => $quarter_info['label'],
                    '{deadline}' => gmdate('F j, Y', strtotime($deadline)),
                    '{days_until}' => $days_until,
                    '{recommended_amount}' => '$' . number_format($recommended, 2),
                    '{ytd_income}' => '$' . number_format($income, 2),
                    '{total_paid}' => '$' . number_format($payments['total_paid'], 2),
                    '{business_name}' => get_option('fs_business_name', get_bloginfo('name')),
                );
                
                $subject = FS_Email_Template::process_variables($template['subject'], $vars);
                $body = FS_Email_Template::process_variables($template['body'], $vars);
                $html_body = FS_Email_Template::wrap_in_template($body, 'Quarterly tax payment reminder', true); // Internal admin email
                
                $headers = array('Content-Type: text/html; charset=UTF-8');
                
                $sent = wp_mail($reminder_email, $subject, $html_body, $headers);
                
                if ($sent) {
                    // Mark as sent
                    update_option($sent_key, current_time('mysql'));
                    error_log('Freelance Studio: Quarterly tax reminder sent for ' . $quarter_key);
                }
            }
        }
    }

    /**
     * Send weekly digest email (called by cron)
     */
    public function send_weekly_digest() {
        FS_Google_Calendar_Service::send_weekly_digest();
    }

    /**
     * Generate recurring expense entries (called by cron)
     */
    public function process_recurring_expenses() {
        $created = FS_Expense::generate_recurring_entries();
        if ($created > 0) {
            error_log('Freelance Studio: Generated ' . $created . ' recurring expense entries');
        }
    }

    /**
     * Generate recurring task entries (called by cron)
     */
    public function process_recurring_tasks() {
        $created = FS_Task::generate_recurring_entries();
        if ($created > 0) {
            error_log('Freelance Studio: Generated ' . $created . ' recurring task entries');
        }
    }

    /**
     * Sync external calendars (called by cron)
     */
    public function process_external_calendar_sync() {
        $synced = FS_External_Calendar::sync_all();
        if ($synced > 0) {
            error_log('Freelance Studio: Synced ' . $synced . ' external calendars');
        }
    }
}
