<?php
/**
 * Fired during plugin activation.
 *
 * This class defines all code necessary to run during the plugin's activation.
 *
 * @package Freelance_Studio
 */

class FS_Activator {

    /**
     * Activate the plugin.
     * Creates all database tables.
     */
    public static function activate() {
        global $wpdb;

        error_log('Freelance Studio: Starting activation');
        
        $charset_collate = $wpdb->get_charset_collate();

        // Include WordPress upgrade functions
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

        // Create all tables
        self::create_clients_table($charset_collate);
        self::create_client_contacts_table($charset_collate);
        self::create_projects_table($charset_collate);
        self::create_milestones_table($charset_collate);
        self::create_tasks_table($charset_collate);
        self::create_time_entries_table($charset_collate);
        self::create_invoices_table($charset_collate);
        self::create_expenses_table($charset_collate);
        self::create_manual_income_table($charset_collate);
        self::create_proposals_table($charset_collate);
        self::create_proposal_section_templates_table($charset_collate);
        self::create_contracts_table($charset_collate);
        self::create_contract_sections_table($charset_collate);
        self::create_project_types_table($charset_collate);
        self::create_task_templates_table($charset_collate);
        self::create_recurring_invoices_table($charset_collate);
        self::create_portal_tokens_table($charset_collate);
        self::create_quarterly_tax_table($charset_collate);
        self::create_annual_deductions_table($charset_collate);
        self::create_user_prefs_table($charset_collate);
        self::create_email_log_table($charset_collate);
        self::create_meeting_types_table($charset_collate);
        self::create_availability_table($charset_collate);
        self::create_bookings_table($charset_collate);
        self::create_forms_table($charset_collate);
        self::create_form_responses_table($charset_collate);
        self::create_preset_services_table($charset_collate);
        self::create_project_files_table($charset_collate);
        self::create_upload_tokens_table($charset_collate);
        self::create_transactions_table($charset_collate);

        // Install default contract sections
        self::install_default_contract_sections();

        // Set default options
        self::set_default_options();
        
        // Verify all required columns exist (catches any missed migrations)
        self::verify_database_schema();
        
        // Run database upgrades
        self::run_upgrades();
        
        // Schedule cron for payment reminders
        if (!wp_next_scheduled('fs_send_payment_reminders')) {
            wp_schedule_event(time(), 'daily', 'fs_send_payment_reminders');
        }
        
        // Schedule cron for recurring expense generation
        if (!wp_next_scheduled('fs_generate_recurring_expenses')) {
            wp_schedule_event(time(), 'daily', 'fs_generate_recurring_expenses');
        }
        
        // Schedule cron for recurring task generation
        if (!wp_next_scheduled('fs_generate_recurring_tasks')) {
            wp_schedule_event(time(), 'daily', 'fs_generate_recurring_tasks');
        }
        
        // Schedule cron for external calendar sync
        if (!wp_next_scheduled('fs_sync_external_calendars')) {
            wp_schedule_event(time(), 'hourly', 'fs_sync_external_calendars');
        }

        // Create the /changelog/ page on freelancestud.io only
        self::maybe_create_changelog_page();

        // Update version
        update_option('fs_version', JEEB_FS_VERSION);
        update_option('fs_db_version', JEEB_FS_VERSION);
        
        // Flush rewrite rules for booking pages
        flush_rewrite_rules();

        error_log('Freelance Studio: Activation completed');
    }

    /**
     * Run database upgrades
     * Called on activation and on admin_init to handle upgrades without reactivation
     */
    public static function run_upgrades() {
        $db_version = get_option('fs_db_version', '1.0.0');
        
        // Upgrade to 1.7.0 - Add activity column to time_entries
        if (version_compare($db_version, '1.7.0', '<')) {
            self::upgrade_to_1_7_0();
        }
        
        // Upgrade to 1.9.2 - Add assigned_to and is_client_visible to tasks
        if (version_compare($db_version, '1.9.2', '<')) {
            self::upgrade_to_1_9_2();
        }
        
        // Upgrade to 1.9.8 - Add logo_id to clients and payment_reminders table
        if (version_compare($db_version, '1.9.8', '<')) {
            self::upgrade_to_1_9_8();
        }
        
        // Upgrade to 1.9.9 - Add pause support to time_entries
        if (version_compare($db_version, '1.9.9', '<')) {
            self::upgrade_to_1_9_9();
        }
        
        // Upgrade to 2.10.0 - Allow NULL project_id for generic Kanban tasks
        if (version_compare($db_version, '2.10.0', '<')) {
            self::upgrade_to_2_10_0();
        }
        
        // Upgrade to 3.1.0 - Add recurring expense support and external calendar URLs
        if (version_compare($db_version, '3.1.0', '<')) {
            self::upgrade_to_3_1_0();
        }
        
        // Upgrade to 3.1.9 - Add paid_date column to invoices
        if (version_compare($db_version, '3.1.9', '<')) {
            self::upgrade_to_3_1_9();
        }
        
        // Upgrade to 3.2.16 - Fix orphaned time entries (marked invoiced but invoice deleted)
        if (version_compare($db_version, '3.2.16', '<')) {
            self::upgrade_to_3_2_16();
        }
        
        // Upgrade to 3.9.9 - Add is_internal column to clients for internal project tracking
        if (version_compare($db_version, '3.9.9', '<')) {
            self::upgrade_to_3_9_9();
        }
        
        // Upgrade to 3.9.15 - Add logo_url column to clients (may have been missed)
        if (version_compare($db_version, '3.9.15', '<')) {
            self::upgrade_to_3_9_15();
        }
        
        // Upgrade to 3.9.16 - Also add is_internal column (may have been missed)
        if (version_compare($db_version, '3.9.16', '<')) {
            self::upgrade_to_3_9_16();
        }
        
        // Upgrade to 5.0.15 - Add project files and upload tokens tables
        if (version_compare($db_version, '5.0.15', '<')) {
            self::upgrade_to_5_0_15();
        }
        
        // Always flush rewrite rules on any upgrade to ensure booking URLs work
        // This is needed because rewrite rules may not persist across updates
        flush_rewrite_rules();
        
        update_option('fs_db_version', JEEB_FS_VERSION);
    }
    
    /**
     * Verify all required database columns exist
     * This catches any columns that may have been missed due to skipped migrations
     * Called on every activation to ensure schema integrity
     */
    public static function verify_database_schema() {
        global $wpdb;
        
        // Define all required columns for each table
        // Format: 'table_suffix' => array('column_name' => 'column_definition')
        $required_columns = array(
            'fs_clients' => array(
                'company_name' => 'varchar(255) DEFAULT NULL',
                'name' => "varchar(255) DEFAULT ''",
                'email' => "varchar(255) DEFAULT ''",
                'phone' => "varchar(50) DEFAULT ''",
                'website' => "varchar(255) DEFAULT ''",
                'address' => 'text DEFAULT NULL',
                'address_2' => "varchar(255) DEFAULT ''",
                'city' => "varchar(100) DEFAULT ''",
                'state' => "varchar(100) DEFAULT ''",
                'zip' => "varchar(20) DEFAULT ''",
                'country' => "varchar(100) DEFAULT ''",
                'notes' => 'text DEFAULT NULL',
                'logo_id' => 'bigint(20) DEFAULT NULL',
                'logo_url' => 'varchar(500) DEFAULT NULL',
                'default_hourly_rate' => 'decimal(10,2) DEFAULT NULL',
                'default_payment_terms' => 'int(11) DEFAULT 30',
                'portal_enabled' => 'tinyint(1) DEFAULT 1',
                'is_internal' => 'tinyint(1) DEFAULT 0',
                'status' => "varchar(20) DEFAULT 'active'",
            ),
            'fs_projects' => array(
                'client_id' => 'bigint(20) DEFAULT NULL',
                'name' => 'varchar(255) NOT NULL',
                'description' => 'text DEFAULT NULL',
                'status' => "varchar(50) DEFAULT 'draft'",
                'archived' => 'tinyint(1) DEFAULT 0',
                'start_date' => 'date DEFAULT NULL',
                'due_date' => 'date DEFAULT NULL',
                'budget' => 'decimal(10,2) DEFAULT NULL',
                'billing_type' => "varchar(50) DEFAULT 'fixed'",
                'billing_frequency' => "varchar(50) DEFAULT 'once'",
                'first_invoice_date' => 'date DEFAULT NULL',
                'deposit_type' => "varchar(20) DEFAULT 'percentage'",
                'deposit_percentage' => 'decimal(5,2) DEFAULT NULL',
                'deposit_paid' => 'tinyint(1) DEFAULT 0',
                'services' => 'longtext DEFAULT NULL',
                'total_amount' => 'decimal(10,2) DEFAULT 0.00',
                'payment_terms' => 'int(11) DEFAULT 30',
                'gcal_event_id' => 'varchar(255) DEFAULT NULL',
                'gcal_end_event_id' => 'varchar(255) DEFAULT NULL',
            ),
            'fs_tasks' => array(
                'project_id' => 'bigint(20) DEFAULT NULL',
                'client_id' => 'bigint(20) DEFAULT NULL',
                'milestone_id' => 'bigint(20) DEFAULT NULL',
                'title' => 'varchar(255) NOT NULL',
                'description' => 'text DEFAULT NULL',
                'status' => "varchar(50) DEFAULT 'pending'",
                'previous_status' => 'varchar(50) DEFAULT NULL',
                'priority' => "varchar(20) DEFAULT 'medium'",
                'due_date' => 'date DEFAULT NULL',
                'estimated_hours' => 'decimal(10,2) DEFAULT NULL',
                'position' => 'int(11) DEFAULT 0',
                'assigned_to' => "varchar(20) DEFAULT 'freelancer'",
                'is_client_visible' => 'tinyint(1) DEFAULT 1',
                'is_recurring' => 'tinyint(1) DEFAULT 0',
                'recurring_frequency' => 'varchar(20) DEFAULT NULL',
                'recurring_days' => 'varchar(100) DEFAULT NULL',
                'recurring_end_date' => 'date DEFAULT NULL',
                'parent_task_id' => 'bigint(20) DEFAULT NULL',
                'hide_until_due' => 'tinyint(1) DEFAULT 0',
                'is_non_billable' => 'tinyint(1) DEFAULT 0',
            ),
            'fs_time_entries' => array(
                'task_id' => 'bigint(20) DEFAULT NULL',
                'user_id' => 'bigint(20) NOT NULL',
                'description' => 'text DEFAULT NULL',
                'start_time' => 'datetime NOT NULL',
                'end_time' => 'datetime DEFAULT NULL',
                'duration' => 'int(11) DEFAULT NULL',
                'hourly_rate' => 'decimal(10,2) DEFAULT NULL',
                'is_billable' => 'tinyint(1) DEFAULT 1',
                'is_invoiced' => 'tinyint(1) DEFAULT 0',
                'invoice_id' => 'bigint(20) DEFAULT NULL',
                'activity' => "varchar(100) DEFAULT 'Development'",
                'is_paused' => 'tinyint(1) DEFAULT 0',
                'paused_duration' => 'int(11) DEFAULT 0',
            ),
            'fs_invoices' => array(
                'project_id' => 'bigint(20) DEFAULT NULL',
                'client_id' => 'bigint(20) NOT NULL',
                'invoice_number' => 'varchar(50) NOT NULL',
                'status' => "varchar(50) DEFAULT 'draft'",
                'issue_date' => 'date NOT NULL',
                'due_date' => 'date NOT NULL',
                'paid_date' => 'date DEFAULT NULL',
                'subtotal' => 'decimal(10,2) DEFAULT 0',
                'tax_rate' => 'decimal(5,2) DEFAULT 0',
                'tax_amount' => 'decimal(10,2) DEFAULT 0',
                'discount_type' => "varchar(20) DEFAULT 'fixed'",
                'discount_value' => 'decimal(10,2) DEFAULT 0',
                'discount_amount' => 'decimal(10,2) DEFAULT 0',
                'total' => 'decimal(10,2) DEFAULT 0',
                'amount_paid' => 'decimal(10,2) DEFAULT 0',
                'notes' => 'text DEFAULT NULL',
                'terms' => 'text DEFAULT NULL',
                'line_items' => 'longtext DEFAULT NULL',
                'view_token' => 'varchar(64) DEFAULT NULL',
                'is_deposit' => 'tinyint(1) DEFAULT 0',
            ),
            'fs_expenses' => array(
                'project_id' => 'bigint(20) DEFAULT NULL',
                'client_id' => 'bigint(20) DEFAULT NULL',
                'category' => 'varchar(100) NOT NULL',
                'description' => 'text DEFAULT NULL',
                'amount' => 'decimal(10,2) NOT NULL',
                'date' => 'date NOT NULL',
                'is_billable' => 'tinyint(1) DEFAULT 0',
                'is_invoiced' => 'tinyint(1) DEFAULT 0',
                'invoice_id' => 'bigint(20) DEFAULT NULL',
                'receipt_url' => 'varchar(500) DEFAULT NULL',
                'vendor' => 'varchar(255) DEFAULT NULL',
                'is_recurring' => 'tinyint(1) DEFAULT 0',
                'recurring_frequency' => 'varchar(50) DEFAULT NULL',
                'recurring_end_date' => 'date DEFAULT NULL',
                'parent_expense_id' => 'bigint(20) DEFAULT NULL',
            ),
            'fs_manual_income' => array(
                'source' => 'varchar(255) NOT NULL',
                'description' => 'text DEFAULT NULL',
                'amount' => 'decimal(10,2) NOT NULL',
                'income_date' => 'date NOT NULL',
                'category' => 'varchar(100) DEFAULT NULL',
                'reference' => 'varchar(255) DEFAULT NULL',
                'notes' => 'text DEFAULT NULL',
            ),
            'fs_proposals' => array(
                'client_id' => 'bigint(20) NOT NULL',
                'proposal_number' => 'varchar(50) NOT NULL',
                'title' => 'varchar(255) NOT NULL',
                'status' => "varchar(50) DEFAULT 'draft'",
                'services' => 'longtext DEFAULT NULL',
                'custom_sections' => 'longtext DEFAULT NULL',
                'pricing_type' => "varchar(20) DEFAULT 'fixed'",
                'total_amount' => 'decimal(10,2) DEFAULT 0.00',
                'deposit_required' => 'tinyint(1) DEFAULT 0',
                'deposit_type' => "varchar(20) DEFAULT 'percentage'",
                'deposit_value' => 'decimal(10,2) DEFAULT 0.00',
                'billing_frequency' => "varchar(20) DEFAULT 'once'",
                'start_date' => 'date DEFAULT NULL',
                'valid_until' => 'date DEFAULT NULL',
                'notes' => 'text DEFAULT NULL',
                'view_token' => 'varchar(64) DEFAULT NULL',
                'viewed_at' => 'datetime DEFAULT NULL',
                'approved_at' => 'datetime DEFAULT NULL',
                'signature_client' => 'longtext DEFAULT NULL',
                'signature_client_name' => 'varchar(255) DEFAULT NULL',
                'signature_ip' => 'varchar(45) DEFAULT NULL',
            ),
            'fs_contracts' => array(
                'client_id' => 'bigint(20) NOT NULL',
                'project_id' => 'bigint(20) DEFAULT NULL',
                'proposal_id' => 'bigint(20) DEFAULT NULL',
                'contract_number' => 'varchar(50) NOT NULL',
                'title' => 'varchar(255) NOT NULL',
                'status' => "varchar(50) DEFAULT 'draft'",
                'services' => 'longtext DEFAULT NULL',
                'enabled_sections' => 'text DEFAULT NULL',
                'terms_content' => 'longtext DEFAULT NULL',
                'start_date' => 'date DEFAULT NULL',
                'end_date' => 'date DEFAULT NULL',
                'valid_until' => 'date DEFAULT NULL',
                'view_token' => 'varchar(64) DEFAULT NULL',
                'viewed_at' => 'datetime DEFAULT NULL',
                'signed_at' => 'datetime DEFAULT NULL',
                'signature_freelancer' => 'longtext DEFAULT NULL',
                'signature_freelancer_name' => 'varchar(255) DEFAULT NULL',
                'signature_freelancer_date' => 'datetime DEFAULT NULL',
                'signature_client' => 'longtext DEFAULT NULL',
                'signature_client_name' => 'varchar(255) DEFAULT NULL',
                'signature_client_date' => 'datetime DEFAULT NULL',
                'signature_client_ip' => 'varchar(45) DEFAULT NULL',
                'client_contact_id' => 'bigint(20) DEFAULT NULL',
                'contact_name' => 'varchar(255) DEFAULT NULL',
                'contact_email' => 'varchar(255) DEFAULT NULL',
                'contact_phone' => 'varchar(50) DEFAULT NULL',
            ),
            'fs_bookings' => array(
                'meeting_type_id' => 'bigint(20) NOT NULL',
                'client_id' => 'bigint(20) DEFAULT NULL',
                'guest_name' => 'varchar(255) NOT NULL',
                'guest_email' => 'varchar(255) NOT NULL',
                'guest_phone' => 'varchar(50) DEFAULT NULL',
                'guest_notes' => 'text DEFAULT NULL',
                'start_time' => 'datetime NOT NULL',
                'end_time' => 'datetime NOT NULL',
                'status' => "varchar(50) DEFAULT 'confirmed'",
                'cancel_token' => 'varchar(64) DEFAULT NULL',
                'google_event_id' => 'varchar(255) DEFAULT NULL',
                'meet_link' => 'varchar(500) DEFAULT NULL',
                'cancelled_at' => 'datetime DEFAULT NULL',
                'cancel_reason' => 'text DEFAULT NULL',
            ),
            'fs_transactions' => array(
                'transaction_date' => 'date NOT NULL',
                'description' => 'text NOT NULL',
                'amount' => 'decimal(10,2) NOT NULL',
                'type' => "varchar(10) NOT NULL DEFAULT 'debit'",
                'bank_reference' => "varchar(255) DEFAULT ''",
                'status' => "varchar(20) NOT NULL DEFAULT 'unmatched'",
                'linked_expense_id' => 'bigint(20) UNSIGNED DEFAULT NULL',
                'linked_invoice_id' => 'bigint(20) UNSIGNED DEFAULT NULL',
                'category' => 'varchar(100) DEFAULT NULL',
                'is_business' => 'tinyint(1) NOT NULL DEFAULT 1',
                'import_batch' => "varchar(64) NOT NULL DEFAULT ''",
                'source_bank' => "varchar(100) DEFAULT ''",
                'raw_data' => 'longtext DEFAULT NULL',
                'notes' => 'text DEFAULT NULL',
            ),
        );
        
        // Check each table and add missing columns
        foreach ($required_columns as $table_suffix => $columns) {
            $table_name = $wpdb->prefix . $table_suffix;
            
            // Check if table exists
            $table_exists = $wpdb->get_var($wpdb->prepare(
                "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
                DB_NAME,
                $table_name
            ));
            
            if (!$table_exists) {
                continue; // Table doesn't exist, will be created by create_*_table methods
            }
            
            // Get existing columns
            $existing_columns = array();
            $results = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
            foreach ($results as $row) {
                $existing_columns[] = $row->Field;
            }
            
            // Add missing columns
            foreach ($columns as $column_name => $column_definition) {
                if (!in_array($column_name, $existing_columns)) {
                    $sql = "ALTER TABLE $table_name ADD COLUMN $column_name $column_definition";
                    $wpdb->query($sql); // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared
                    error_log("Freelance Studio: Added missing column '$column_name' to $table_suffix");
                    
                    // Add index for is_internal column
                    if ($column_name === 'is_internal') {
                        $wpdb->query("ALTER TABLE $table_name ADD INDEX is_internal (is_internal)");
                    }
                }
            }
        }
    }
    
    /**
     * Upgrade to version 3.1.9
     * Add paid_date column to invoices table
     */
    private static function upgrade_to_3_1_9() {
        global $wpdb;
        
        $invoices_table = $wpdb->prefix . 'fs_invoices';
        
        // Check if paid_date column exists
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'paid_date'",
            DB_NAME,
            $invoices_table
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $invoices_table ADD COLUMN paid_date date DEFAULT NULL AFTER due_date");
            error_log('Freelance Studio: Added paid_date column to invoices table');
        }
        
        // Also make invoice_date nullable for draft invoices
        $wpdb->query("ALTER TABLE $invoices_table MODIFY COLUMN invoice_date date DEFAULT NULL");
    }
    
    /**
     * Upgrade to version 3.2.16
     * Fix orphaned time entries (marked as invoiced but invoice was deleted)
     */
    private static function upgrade_to_3_2_16() {
        global $wpdb;
        
        $time_table = $wpdb->prefix . 'fs_time_entries';
        $invoice_table = $wpdb->prefix . 'fs_invoices';
        
        // Find and fix time entries that are marked invoiced=1 but whose invoice_id doesn't exist
        $fixed = $wpdb->query(
            "UPDATE {$time_table} t 
             LEFT JOIN {$invoice_table} i ON t.invoice_id = i.id 
             SET t.invoiced = 0, t.invoice_id = NULL 
             WHERE t.invoiced = 1 
             AND (t.invoice_id IS NULL OR i.id IS NULL)"
        );
        
        if ($fixed > 0) {
            error_log("Freelance Studio: Fixed {$fixed} orphaned time entries during upgrade to 3.2.16");
        }
    }
    
    /**
     * Upgrade to version 3.9.9
     * Add is_internal column to clients for internal project tracking
     */
    private static function upgrade_to_3_9_9() {
        global $wpdb;
        
        $clients_table = $wpdb->prefix . 'fs_clients';
        
        // Check if is_internal column exists
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_internal'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN is_internal tinyint(1) DEFAULT 0");
            $wpdb->query("ALTER TABLE $clients_table ADD INDEX is_internal (is_internal)");
            error_log('Freelance Studio: Added is_internal column to clients table');
        }
        
        // Check if logo_url column exists (may have been missed in earlier migrations)
        $logo_url_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'logo_url'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($logo_url_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN logo_url varchar(500) DEFAULT NULL");
            error_log('Freelance Studio: Added logo_url column to clients table');
        }
    }
    
    /**
     * Upgrade to version 3.9.15
     * Ensure logo_url and is_internal columns exist on clients table
     */
    private static function upgrade_to_3_9_15() {
        global $wpdb;
        
        $clients_table = $wpdb->prefix . 'fs_clients';
        
        // Check if logo_url column exists (may have been missed in earlier migrations)
        $logo_url_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'logo_url'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($logo_url_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN logo_url varchar(500) DEFAULT NULL");
            error_log('Freelance Studio: Added logo_url column to clients table');
        }
        
        // Check if is_internal column exists (may have been missed in earlier migrations)
        $is_internal_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_internal'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($is_internal_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN is_internal tinyint(1) DEFAULT 0");
            $wpdb->query("ALTER TABLE $clients_table ADD INDEX is_internal (is_internal)");
            error_log('Freelance Studio: Added is_internal column to clients table');
        }
    }
    
    /**
     * Upgrade to version 3.9.16
     * Ensure is_internal column exists on clients table (retry for 3.9.15 users)
     */
    private static function upgrade_to_3_9_16() {
        global $wpdb;
        
        $clients_table = $wpdb->prefix . 'fs_clients';
        
        // Check if is_internal column exists
        $is_internal_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_internal'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($is_internal_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN is_internal tinyint(1) DEFAULT 0");
            $wpdb->query("ALTER TABLE $clients_table ADD INDEX is_internal (is_internal)");
            error_log('Freelance Studio: Added is_internal column to clients table');
        }
    }
    
    /**
     * Upgrade to version 5.0.15
     * Add project files and upload tokens tables for file management feature
     */
    private static function upgrade_to_5_0_15() {
        global $wpdb;
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        $charset_collate = $wpdb->get_charset_collate();
        
        // Create project files table
        $files_table = $wpdb->prefix . 'fs_project_files';
        if ($wpdb->get_var("SHOW TABLES LIKE '$files_table'") !== $files_table) {
            $sql = "CREATE TABLE IF NOT EXISTS $files_table (
                id bigint(20) NOT NULL AUTO_INCREMENT,
                project_id bigint(20) NOT NULL,
                attachment_id bigint(20) NOT NULL,
                category varchar(50) DEFAULT 'general',
                uploaded_by bigint(20) DEFAULT NULL,
                uploaded_by_client tinyint(1) DEFAULT 0,
                shared_with_client tinyint(1) DEFAULT 0,
                notes text DEFAULT NULL,
                created_at datetime DEFAULT CURRENT_TIMESTAMP,
                updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                KEY project_id (project_id),
                KEY attachment_id (attachment_id),
                KEY category (category),
                KEY shared_with_client (shared_with_client)
            ) $charset_collate;";
            dbDelta($sql);
            error_log('Freelance Studio: Created fs_project_files table');
        }
        
        // Create upload tokens table
        $tokens_table = $wpdb->prefix . 'fs_upload_tokens';
        if ($wpdb->get_var("SHOW TABLES LIKE '$tokens_table'") !== $tokens_table) {
            $sql = "CREATE TABLE IF NOT EXISTS $tokens_table (
                id bigint(20) NOT NULL AUTO_INCREMENT,
                project_id bigint(20) NOT NULL,
                token varchar(64) NOT NULL,
                expires_at datetime NOT NULL,
                used_at datetime DEFAULT NULL,
                created_at datetime DEFAULT CURRENT_TIMESTAMP,
                PRIMARY KEY (id),
                UNIQUE KEY token (token),
                KEY project_id (project_id),
                KEY expires_at (expires_at)
            ) $charset_collate;";
            dbDelta($sql);
            error_log('Freelance Studio: Created fs_upload_tokens table');
        }
    }
    
    /**
     * Upgrade to version 3.1.0
     * Add recurring expense support and external calendar URL support
     */
    private static function upgrade_to_3_1_0() {
        global $wpdb;
        
        // Add recurring expense columns
        $expenses_table = $wpdb->prefix . 'fs_expenses';
        
        // Check if is_recurring column exists
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_recurring'",
            DB_NAME,
            $expenses_table
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $expenses_table ADD COLUMN is_recurring tinyint(1) DEFAULT 0 AFTER is_reimbursable");
            $wpdb->query("ALTER TABLE $expenses_table ADD COLUMN recurring_frequency varchar(20) DEFAULT NULL AFTER is_recurring");
            $wpdb->query("ALTER TABLE $expenses_table ADD COLUMN recurring_day int(2) DEFAULT NULL AFTER recurring_frequency");
            $wpdb->query("ALTER TABLE $expenses_table ADD COLUMN recurring_end_date date DEFAULT NULL AFTER recurring_day");
            $wpdb->query("ALTER TABLE $expenses_table ADD COLUMN parent_expense_id bigint(20) DEFAULT NULL AFTER recurring_end_date");
            $wpdb->query("ALTER TABLE $expenses_table ADD INDEX is_recurring (is_recurring)");
            $wpdb->query("ALTER TABLE $expenses_table ADD INDEX parent_expense_id (parent_expense_id)");
            error_log('Freelance Studio: Added recurring expense columns to expenses table');
        }
        
        // Create external calendars table
        $calendars_table = $wpdb->prefix . 'fs_external_calendars';
        $charset_collate = $wpdb->get_charset_collate();
        
        $sql = "CREATE TABLE IF NOT EXISTS $calendars_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            url varchar(1000) NOT NULL,
            color varchar(7) DEFAULT '#6b7280',
            is_active tinyint(1) DEFAULT 1,
            affects_availability tinyint(1) DEFAULT 1,
            last_synced datetime DEFAULT NULL,
            sync_error text DEFAULT NULL,
            cached_events longtext DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY is_active (is_active)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
        
        error_log('Freelance Studio: Created external_calendars table');
    }
    
    /**
     * Upgrade to version 2.10.0
     * Allow NULL project_id in tasks table for generic Kanban tasks
     */
    private static function upgrade_to_2_10_0() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_tasks';
        
        // Modify project_id to allow NULL values
        $wpdb->query("ALTER TABLE $table_name MODIFY project_id bigint(20) DEFAULT NULL");
        
        error_log('Freelance Studio: Modified tasks table to allow NULL project_id for Kanban support');
    }
    
    /**
     * Upgrade to version 1.7.0
     * Adds activity column to time_entries table
     */
    private static function upgrade_to_1_7_0() {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'fs_time_entries';
        
        // Check if activity column exists
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'activity'",
            DB_NAME,
            $table_name
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN activity varchar(50) DEFAULT 'other'");
            $wpdb->query("ALTER TABLE $table_name ADD INDEX activity (activity)");
            error_log('Freelance Studio: Added activity column to time_entries table');
        }
    }
    
    /**
     * Upgrade to version 1.9.2
     * Adds assigned_to and is_client_visible columns to tasks table
     */
    private static function upgrade_to_1_9_2() {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'fs_tasks';
        
        // Check if assigned_to column exists
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'assigned_to'",
            DB_NAME,
            $table_name
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN assigned_to varchar(20) DEFAULT 'freelancer'");
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN is_client_visible tinyint(1) DEFAULT 1");
            $wpdb->query("ALTER TABLE $table_name ADD INDEX assigned_to (assigned_to)");
            error_log('Freelance Studio: Added assigned_to and is_client_visible columns to tasks table');
        }
    }
    
    /**
     * Upgrade to version 1.9.8
     * Adds logo_id to clients table and creates payment_reminders table
     */
    private static function upgrade_to_1_9_8() {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        
        // Add logo_id to clients table
        $clients_table = $wpdb->prefix . 'fs_clients';
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'logo_id'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN logo_id bigint(20) DEFAULT NULL");
            error_log('Freelance Studio: Added logo_id column to clients table');
        }
        
        // Add logo_url column to clients table (v3.1.32)
        $logo_url_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'logo_url'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($logo_url_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN logo_url varchar(500) DEFAULT NULL");
            error_log('Freelance Studio: Added logo_url column to clients table');
        }
        
        // Add is_internal column to clients table (v3.9.9)
        $is_internal_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'is_internal'",
            DB_NAME,
            $clients_table
        ));
        
        if (empty($is_internal_exists)) {
            $wpdb->query("ALTER TABLE $clients_table ADD COLUMN is_internal tinyint(1) DEFAULT 0");
            $wpdb->query("ALTER TABLE $clients_table ADD INDEX is_internal (is_internal)");
            error_log('Freelance Studio: Added is_internal column to clients table');
        }
        
        // Create payment_reminders table
        $reminders_table = $wpdb->prefix . 'fs_payment_reminders';
        $sql = "CREATE TABLE IF NOT EXISTS $reminders_table (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            invoice_id bigint(20) NOT NULL,
            reminder_type varchar(50) NOT NULL,
            scheduled_date date NOT NULL,
            sent_at datetime DEFAULT NULL,
            status varchar(20) DEFAULT 'pending',
            custom_message text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY invoice_id (invoice_id),
            KEY scheduled_date (scheduled_date),
            KEY status (status)
        ) $charset_collate;";
        
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
        
        // Add reminder settings options
        add_option('fs_reminder_enabled', '1');
        add_option('fs_reminder_before_due', '3');
        add_option('fs_reminder_after_due_1', '1');
        add_option('fs_reminder_after_due_2', '7');
        add_option('fs_reminder_after_due_3', '14');
        add_option('fs_reminder_subject_before', 'Friendly reminder: Invoice {invoice_number} due soon');
        add_option('fs_reminder_subject_overdue', 'Invoice {invoice_number} is overdue');
        
        error_log('Freelance Studio: Created payment_reminders table and added reminder settings');
    }
    
    /**
     * Upgrade to version 1.9.9
     * Adds pause support to time_entries table
     */
    private static function upgrade_to_1_9_9() {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'fs_time_entries';
        
        // Add paused_at column
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'paused_at'",
            DB_NAME,
            $table_name
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN paused_at datetime DEFAULT NULL");
            error_log('Freelance Studio: Added paused_at column to time_entries table');
        }
        
        // Add paused_duration column (accumulated seconds from pause sessions)
        $column_exists = $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = 'paused_duration'",
            DB_NAME,
            $table_name
        ));
        
        if (empty($column_exists)) {
            $wpdb->query("ALTER TABLE $table_name ADD COLUMN paused_duration int DEFAULT 0");
            error_log('Freelance Studio: Added paused_duration column to time_entries table');
        }
    }

    /**
     * Clients table
     */
    private static function create_clients_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_clients';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            company_name varchar(255) DEFAULT NULL,
            name varchar(255) NOT NULL,
            email varchar(255) DEFAULT NULL,
            phone varchar(50) DEFAULT NULL,
            website varchar(500) DEFAULT NULL,
            address text DEFAULT NULL,
            address_2 varchar(255) DEFAULT NULL,
            city varchar(100) DEFAULT NULL,
            state varchar(100) DEFAULT NULL,
            zip varchar(20) DEFAULT NULL,
            country varchar(100) DEFAULT NULL,
            notes text DEFAULT NULL,
            default_hourly_rate decimal(10,2) DEFAULT NULL,
            default_payment_terms int DEFAULT 30,
            portal_enabled tinyint(1) DEFAULT 1,
            portal_token varchar(64) DEFAULT NULL,
            is_internal tinyint(1) DEFAULT 0,
            status varchar(20) DEFAULT 'active',
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY status (status),
            KEY portal_token (portal_token),
            KEY is_internal (is_internal)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Client contacts table
     */
    private static function create_client_contacts_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_client_contacts';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) NOT NULL,
            name varchar(255) NOT NULL,
            email varchar(255) DEFAULT NULL,
            phone varchar(50) DEFAULT NULL,
            role varchar(100) DEFAULT NULL,
            is_primary tinyint(1) DEFAULT 0,
            is_billing_contact tinyint(1) DEFAULT 0,
            is_project_contact tinyint(1) DEFAULT 0,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY client_id (client_id),
            KEY is_primary (is_primary)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Projects table (replaces gigs)
     */
    private static function create_projects_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_projects';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) DEFAULT NULL,
            contract_id bigint(20) DEFAULT NULL,
            proposal_id bigint(20) DEFAULT NULL,
            project_type_id bigint(20) DEFAULT NULL,
            name varchar(255) NOT NULL,
            description text DEFAULT NULL,
            status varchar(50) DEFAULT 'draft',
            archived tinyint(1) DEFAULT 0,
            billing_type varchar(50) DEFAULT 'fixed',
            billing_frequency varchar(50) DEFAULT 'once',
            first_invoice_date date DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            fixed_price decimal(10,2) DEFAULT NULL,
            budget decimal(10,2) DEFAULT NULL,
            deposit_type varchar(20) DEFAULT 'percentage',
            deposit_percentage decimal(5,2) DEFAULT 0.00,
            deposit_amount decimal(10,2) DEFAULT 0.00,
            deposit_paid tinyint(1) DEFAULT 0,
            services longtext DEFAULT NULL,
            total_amount decimal(10,2) DEFAULT 0.00,
            payment_terms int(11) DEFAULT 30,
            start_date date DEFAULT NULL,
            due_date date DEFAULT NULL,
            completed_date date DEFAULT NULL,
            is_retainer tinyint(1) DEFAULT 0,
            retainer_amount decimal(10,2) DEFAULT NULL,
            retainer_hours decimal(10,2) DEFAULT NULL,
            retainer_rollover tinyint(1) DEFAULT 1,
            retainer_reset_day tinyint(2) DEFAULT NULL,
            gcal_event_id varchar(255) DEFAULT NULL,
            gcal_end_event_id varchar(255) DEFAULT NULL,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY client_id (client_id),
            KEY contract_id (contract_id),
            KEY proposal_id (proposal_id),
            KEY status (status),
            KEY archived (archived),
            KEY billing_type (billing_type)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Milestones table
     */
    private static function create_milestones_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_milestones';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) NOT NULL,
            name varchar(255) NOT NULL,
            description text DEFAULT NULL,
            due_date date DEFAULT NULL,
            completed_date date DEFAULT NULL,
            status varchar(50) DEFAULT 'pending',
            invoice_on_completion tinyint(1) DEFAULT 0,
            invoice_amount decimal(10,2) DEFAULT NULL,
            invoice_percentage decimal(5,2) DEFAULT NULL,
            invoice_id bigint(20) DEFAULT NULL,
            position int DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_id (project_id),
            KEY status (status),
            KEY invoice_id (invoice_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Tasks table
     */
    private static function create_tasks_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_tasks';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) NOT NULL,
            milestone_id bigint(20) DEFAULT NULL,
            template_id bigint(20) DEFAULT NULL,
            title varchar(255) NOT NULL,
            description text DEFAULT NULL,
            status varchar(50) DEFAULT 'pending',
            priority varchar(20) DEFAULT 'medium',
            due_date date DEFAULT NULL,
            completed_date date DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            actual_hours decimal(10,2) DEFAULT NULL,
            assigned_to varchar(20) DEFAULT 'freelancer',
            is_client_visible tinyint(1) DEFAULT 1,
            position int DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_id (project_id),
            KEY milestone_id (milestone_id),
            KEY status (status),
            KEY assigned_to (assigned_to)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Time entries table
     */
    private static function create_time_entries_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_time_entries';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) NOT NULL,
            task_id bigint(20) DEFAULT NULL,
            user_id bigint(20) NOT NULL,
            description text DEFAULT NULL,
            activity varchar(50) DEFAULT 'other',
            start_time datetime NOT NULL,
            end_time datetime DEFAULT NULL,
            duration int DEFAULT 0,
            paused_at datetime DEFAULT NULL,
            paused_duration int DEFAULT 0,
            billable tinyint(1) DEFAULT 1,
            hourly_rate decimal(10,2) DEFAULT NULL,
            invoiced tinyint(1) DEFAULT 0,
            invoice_id bigint(20) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_id (project_id),
            KEY task_id (task_id),
            KEY user_id (user_id),
            KEY invoice_id (invoice_id),
            KEY invoiced (invoiced),
            KEY activity (activity)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Invoices table
     */
    private static function create_invoices_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_invoices';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) NOT NULL,
            project_id bigint(20) DEFAULT NULL,
            contract_id bigint(20) DEFAULT NULL,
            proposal_id bigint(20) DEFAULT NULL,
            invoice_number varchar(50) NOT NULL,
            invoice_date date DEFAULT NULL,
            due_date date NOT NULL,
            paid_date date DEFAULT NULL,
            status varchar(20) DEFAULT 'draft',
            subtotal decimal(10,2) DEFAULT 0.00,
            tax_rate decimal(5,2) DEFAULT 0.00,
            tax_amount decimal(10,2) DEFAULT 0.00,
            discount_type varchar(20) DEFAULT NULL,
            discount_value decimal(10,2) DEFAULT 0.00,
            discount_amount decimal(10,2) DEFAULT 0.00,
            total decimal(10,2) DEFAULT 0.00,
            amount_paid decimal(10,2) DEFAULT 0.00,
            balance_due decimal(10,2) DEFAULT 0.00,
            notes text DEFAULT NULL,
            items longtext DEFAULT NULL,
            expenses longtext DEFAULT NULL,
            is_recurring tinyint(1) DEFAULT 0,
            recurring_id bigint(20) DEFAULT NULL,
            is_deposit tinyint(1) DEFAULT 0,
            sent_at datetime DEFAULT NULL,
            viewed_at datetime DEFAULT NULL,
            paid_at datetime DEFAULT NULL,
            reminder_sent_at datetime DEFAULT NULL,
            payment_link varchar(500) DEFAULT NULL,
            view_token varchar(64) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY invoice_number (invoice_number),
            KEY client_id (client_id),
            KEY project_id (project_id),
            KEY status (status),
            KEY view_token (view_token)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Expenses table
     */
    private static function create_expenses_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_expenses';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) DEFAULT NULL,
            invoice_id bigint(20) DEFAULT NULL,
            expense_date date NOT NULL,
            expense_name varchar(255) NOT NULL,
            category varchar(100) NOT NULL,
            amount decimal(10,2) NOT NULL,
            is_billable tinyint(1) DEFAULT 0,
            is_reimbursable tinyint(1) DEFAULT 0,
            is_recurring tinyint(1) DEFAULT 0,
            recurring_frequency varchar(20) DEFAULT NULL,
            recurring_day int(2) DEFAULT NULL,
            recurring_end_date date DEFAULT NULL,
            parent_expense_id bigint(20) DEFAULT NULL,
            notes text DEFAULT NULL,
            receipt_id bigint(20) DEFAULT NULL,
            receipt_filename varchar(255) DEFAULT NULL,
            receipt_path varchar(500) DEFAULT NULL,
            receipt_url varchar(500) DEFAULT NULL,
            receipt_mime_type varchar(100) DEFAULT NULL,
            receipt_file_size bigint(20) DEFAULT NULL,
            receipt_upload_date datetime DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY expense_date (expense_date),
            KEY category (category),
            KEY project_id (project_id),
            KEY invoice_id (invoice_id),
            KEY is_recurring (is_recurring),
            KEY parent_expense_id (parent_expense_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Manual Income table
     */
    private static function create_manual_income_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_manual_income';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            source varchar(255) NOT NULL,
            description text DEFAULT NULL,
            amount decimal(10,2) NOT NULL,
            income_date date NOT NULL,
            category varchar(100) DEFAULT NULL,
            reference varchar(255) DEFAULT NULL,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY income_date (income_date),
            KEY category (category),
            KEY source (source)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Proposals table (Phase 2 - created now for schema completeness)
     */
    private static function create_proposals_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_proposals';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) NOT NULL,
            proposal_number varchar(50) NOT NULL,
            title varchar(255) NOT NULL,
            status varchar(50) DEFAULT 'draft',
            services longtext DEFAULT NULL,
            custom_sections longtext DEFAULT NULL,
            pricing_type varchar(20) DEFAULT 'fixed',
            hourly_rate decimal(10,2) DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            total_amount decimal(10,2) DEFAULT 0.00,
            deposit_required tinyint(1) DEFAULT 0,
            deposit_type varchar(20) DEFAULT 'percentage',
            deposit_value decimal(10,2) DEFAULT 0.00,
            billing_frequency varchar(20) DEFAULT 'once',
            first_invoice_date date DEFAULT NULL,
            start_date date DEFAULT NULL,
            valid_until date DEFAULT NULL,
            notes text DEFAULT NULL,
            internal_notes text DEFAULT NULL,
            revision_notes text DEFAULT NULL,
            revision_requested_at datetime DEFAULT NULL,
            sent_at datetime DEFAULT NULL,
            viewed_at datetime DEFAULT NULL,
            approved_at datetime DEFAULT NULL,
            rejected_at datetime DEFAULT NULL,
            rejection_reason text DEFAULT NULL,
            signature_client longtext DEFAULT NULL,
            signature_client_name varchar(255) DEFAULT NULL,
            signature_date datetime DEFAULT NULL,
            signature_ip varchar(45) DEFAULT NULL,
            view_token varchar(64) DEFAULT NULL,
            contract_id bigint(20) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY proposal_number (proposal_number),
            KEY client_id (client_id),
            KEY status (status),
            KEY view_token (view_token)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Proposal Section Templates table (v4.0.13)
     */
    private static function create_proposal_section_templates_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_proposal_section_templates';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            title varchar(255) NOT NULL,
            content longtext NOT NULL,
            position varchar(20) DEFAULT 'before_pricing',
            sort_order int(11) DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY position (position),
            KEY sort_order (sort_order)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Contracts table (Phase 3 - created now for schema completeness)
     */
    private static function create_contracts_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_contracts';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            proposal_id bigint(20) DEFAULT NULL,
            client_id bigint(20) NOT NULL,
            project_id bigint(20) DEFAULT NULL,
            contract_number varchar(50) NOT NULL,
            title varchar(255) NOT NULL,
            status varchar(50) DEFAULT 'draft',
            services longtext DEFAULT NULL,
            pricing_type varchar(20) DEFAULT 'fixed',
            hourly_rate decimal(10,2) DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            total_amount decimal(10,2) DEFAULT 0.00,
            deposit_required tinyint(1) DEFAULT 0,
            deposit_type varchar(20) DEFAULT 'percentage',
            deposit_value decimal(10,2) DEFAULT 0.00,
            billing_frequency varchar(20) DEFAULT 'once',
            first_invoice_date date DEFAULT NULL,
            enabled_sections text DEFAULT NULL,
            terms_content longtext DEFAULT NULL,
            start_date date DEFAULT NULL,
            end_date date DEFAULT NULL,
            valid_until date DEFAULT NULL,
            notes text DEFAULT NULL,
            internal_notes text DEFAULT NULL,
            send_to_contacts text DEFAULT NULL,
            signature_freelancer longtext DEFAULT NULL,
            signature_freelancer_name varchar(255) DEFAULT NULL,
            signature_freelancer_date datetime DEFAULT NULL,
            signature_client longtext DEFAULT NULL,
            signature_client_name varchar(255) DEFAULT NULL,
            signature_client_date datetime DEFAULT NULL,
            signature_client_ip varchar(45) DEFAULT NULL,
            client_contact_id bigint(20) DEFAULT NULL,
            contact_name varchar(255) DEFAULT NULL,
            contact_email varchar(255) DEFAULT NULL,
            contact_phone varchar(50) DEFAULT NULL,
            signed_pdf_path varchar(500) DEFAULT NULL,
            sent_at datetime DEFAULT NULL,
            viewed_at datetime DEFAULT NULL,
            signed_at datetime DEFAULT NULL,
            view_token varchar(64) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY contract_number (contract_number),
            KEY proposal_id (proposal_id),
            KEY client_id (client_id),
            KEY project_id (project_id),
            KEY status (status),
            KEY view_token (view_token)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Contract sections table - stores the template sections
     */
    private static function create_contract_sections_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_contract_sections';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            section_key varchar(100) NOT NULL,
            title varchar(255) NOT NULL,
            content longtext NOT NULL,
            sort_order int(11) DEFAULT 0,
            is_required tinyint(1) DEFAULT 0,
            is_active tinyint(1) DEFAULT 1,
            category varchar(50) DEFAULT 'terms',
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY section_key (section_key),
            KEY sort_order (sort_order),
            KEY category (category)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Project types (templates) table
     */
    private static function create_project_types_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_project_types';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            description text DEFAULT NULL,
            default_billing_type varchar(20) DEFAULT 'hourly',
            default_hourly_rate decimal(10,2) DEFAULT NULL,
            default_tasks longtext DEFAULT NULL,
            default_milestones longtext DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            typical_price_min decimal(10,2) DEFAULT NULL,
            typical_price_max decimal(10,2) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Task templates table
     */
    private static function create_task_templates_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_task_templates';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            description text DEFAULT NULL,
            estimated_hours decimal(10,2) DEFAULT NULL,
            category varchar(100) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY category (category)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Preset services table
     */
    private static function create_preset_services_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_preset_services';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            description text DEFAULT NULL,
            pricing_type varchar(20) DEFAULT 'fixed',
            default_rate decimal(10,2) DEFAULT 0.00,
            default_quantity decimal(10,2) DEFAULT 1.00,
            sort_order int(11) DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY sort_order (sort_order)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Project files table
     */
    private static function create_project_files_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_project_files';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) NOT NULL,
            attachment_id bigint(20) NOT NULL,
            category varchar(50) DEFAULT 'general',
            uploaded_by bigint(20) DEFAULT NULL,
            uploaded_by_client tinyint(1) DEFAULT 0,
            shared_with_client tinyint(1) DEFAULT 0,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY project_id (project_id),
            KEY attachment_id (attachment_id),
            KEY category (category),
            KEY shared_with_client (shared_with_client)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Upload tokens table (for client file uploads)
     */
    private static function create_upload_tokens_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_upload_tokens';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            project_id bigint(20) NOT NULL,
            token varchar(64) NOT NULL,
            expires_at datetime NOT NULL,
            used_at datetime DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY token (token),
            KEY project_id (project_id),
            KEY expires_at (expires_at)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Recurring invoices table
     */
    private static function create_recurring_invoices_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_recurring_invoices';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) NOT NULL,
            project_id bigint(20) DEFAULT NULL,
            name varchar(255) NOT NULL,
            frequency varchar(20) DEFAULT 'monthly',
            amount decimal(10,2) NOT NULL,
            items longtext DEFAULT NULL,
            next_invoice_date date DEFAULT NULL,
            last_invoice_date date DEFAULT NULL,
            end_date date DEFAULT NULL,
            status varchar(20) DEFAULT 'active',
            auto_send tinyint(1) DEFAULT 0,
            include_time_entries tinyint(1) DEFAULT 0,
            time_grouping varchar(20) DEFAULT 'task',
            mark_time_invoiced tinyint(1) DEFAULT 1,
            invoices_generated int DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY client_id (client_id),
            KEY project_id (project_id),
            KEY status (status),
            KEY next_invoice_date (next_invoice_date)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Portal tokens table
     */
    private static function create_portal_tokens_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_portal_tokens';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            client_id bigint(20) NOT NULL,
            token varchar(64) NOT NULL,
            expires_at datetime DEFAULT NULL,
            last_accessed datetime DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY token (token),
            KEY client_id (client_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Quarterly tax payments table
     */
    private static function create_quarterly_tax_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_quarterly_tax_payments';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) NOT NULL,
            year int(4) NOT NULL,
            quarter varchar(2) NOT NULL,
            estimated_amount decimal(10,2) DEFAULT 0.00,
            actual_payment decimal(10,2) DEFAULT 0.00,
            payment_date date DEFAULT NULL,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY user_id (user_id),
            KEY year_quarter (year, quarter)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Annual tax deductions table
     */
    private static function create_annual_deductions_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_annual_deductions';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) NOT NULL,
            year int(4) NOT NULL,
            name varchar(255) NOT NULL,
            category varchar(100) NOT NULL,
            annual_amount decimal(10,2) DEFAULT 0.00,
            deduction_percentage decimal(5,2) DEFAULT 100.00,
            notes text DEFAULT NULL,
            is_active tinyint(1) DEFAULT 1,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY user_id (user_id),
            KEY year (year),
            KEY category (category),
            KEY user_year (user_id, year)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * User dashboard preferences table
     */
    private static function create_user_prefs_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_user_prefs';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) NOT NULL,
            card_order text,
            hidden_cards text,
            theme varchar(50) DEFAULT 'default',
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY user_id (user_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Email log table
     */
    private static function create_email_log_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_email_log';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            recipient varchar(255) NOT NULL,
            subject varchar(255) NOT NULL,
            type varchar(50) DEFAULT NULL,
            related_type varchar(50) DEFAULT NULL,
            related_id bigint(20) DEFAULT NULL,
            sent_at datetime DEFAULT CURRENT_TIMESTAMP,
            opened_at datetime DEFAULT NULL,
            status varchar(20) DEFAULT 'sent',
            PRIMARY KEY (id),
            KEY recipient (recipient),
            KEY type (type),
            KEY related_type_id (related_type, related_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Meeting types table
     */
    private static function create_meeting_types_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_meeting_types';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            slug varchar(255) NOT NULL,
            description text DEFAULT NULL,
            duration int NOT NULL DEFAULT 30,
            color varchar(7) DEFAULT '#4173B0',
            price decimal(10,2) DEFAULT NULL,
            location_type varchar(20) DEFAULT 'video',
            location_details text DEFAULT NULL,
            buffer_before int DEFAULT 0,
            buffer_after int DEFAULT 0,
            min_notice_hours int DEFAULT 24,
            max_days_advance int DEFAULT 60,
            is_active tinyint(1) DEFAULT 1,
            is_public tinyint(1) DEFAULT 1,
            sort_order int DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY slug (slug),
            KEY is_active (is_active),
            KEY is_public (is_public)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Availability table (weekly schedule and exceptions)
     */
    private static function create_availability_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_availability';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            type varchar(20) NOT NULL DEFAULT 'weekly',
            day_of_week tinyint(1) DEFAULT NULL,
            specific_date date DEFAULT NULL,
            start_time time DEFAULT NULL,
            end_time time DEFAULT NULL,
            is_available tinyint(1) DEFAULT 1,
            note varchar(255) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY type (type),
            KEY day_of_week (day_of_week),
            KEY specific_date (specific_date)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Bookings table
     */
    private static function create_bookings_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_bookings';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            meeting_type_id bigint(20) NOT NULL,
            client_id bigint(20) DEFAULT NULL,
            guest_name varchar(255) DEFAULT NULL,
            guest_email varchar(255) DEFAULT NULL,
            guest_phone varchar(50) DEFAULT NULL,
            start_datetime datetime NOT NULL,
            end_datetime datetime NOT NULL,
            timezone varchar(100) DEFAULT 'America/New_York',
            status varchar(50) DEFAULT 'confirmed',
            notes text DEFAULT NULL,
            gcal_event_id varchar(255) DEFAULT NULL,
            meet_link varchar(500) DEFAULT NULL,
            confirmation_token varchar(64) DEFAULT NULL,
            cancellation_reason text DEFAULT NULL,
            cancelled_at datetime DEFAULT NULL,
            cancelled_by varchar(50) DEFAULT NULL,
            reminder_sent tinyint(1) DEFAULT 0,
            wants_reminder tinyint(1) DEFAULT 1,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY meeting_type_id (meeting_type_id),
            KEY client_id (client_id),
            KEY start_datetime (start_datetime),
            KEY status (status),
            KEY confirmation_token (confirmation_token),
            KEY gcal_event_id (gcal_event_id)
        ) $charset_collate;";

        dbDelta($sql);
    }

    /**
     * Set default options
     */
    private static function set_default_options() {
        // Business settings
        add_option('fs_business_name', '');
        add_option('fs_business_email', '');
        add_option('fs_business_phone', '');
        add_option('fs_business_website', '');
        add_option('fs_business_address', '');
        add_option('fs_business_city', '');
        add_option('fs_business_state', '');
        add_option('fs_business_zip', '');
        add_option('fs_business_country', 'United States');
        add_option('fs_business_logo', '');
        add_option('fs_business_logo_light', '');

        // Default rates
        add_option('fs_default_hourly_rate', '75.00');
        add_option('fs_default_payment_terms', '30');
        add_option('fs_default_deposit_percentage', '50');

        // Tax settings
        add_option('fs_tax_state', '');
        add_option('fs_federal_tax_bracket', '22');

        // Invoice settings
        add_option('fs_invoice_prefix', 'INV');
        add_option('fs_invoice_primary_color', '#2271b1');
        add_option('fs_invoice_primary_color_mode', 'solid');
        add_option('fs_invoice_primary_color_2', '#1a5276');
        add_option('fs_invoice_gradient_direction', '135');
        add_option('fs_payment_instructions', '');

        // Proposal settings
        add_option('fs_proposal_prefix', 'PROP');
        add_option('fs_proposal_validity_days', '30');

        // Contract settings
        add_option('fs_contract_prefix', 'CON');
        add_option('fs_freelancer_signature', '');
        
        // Booking settings
        add_option('fs_booking_enabled', '1');
        add_option('fs_booking_page_title', 'Book a Meeting');
        add_option('fs_booking_page_description', 'Schedule a time to meet with me.');
        add_option('fs_booking_timezone', wp_timezone_string());
        add_option('fs_booking_confirmation_email', '1');
        add_option('fs_booking_reminder_hours', '1');
        add_option('fs_booking_auto_google_meet', '0');
    }

    /**
     * Install default contract sections from template
     */
    private static function install_default_contract_sections() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_contract_sections';

        // Check if sections already exist
        $count = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
        if ($count > 0) {
            return; // Already installed
        }

        // Define all sections from the contract template
        $sections = self::get_default_contract_sections();

        foreach ($sections as $index => $section) {
            $wpdb->insert($table_name, array(
                'section_key' => $section['key'],
                'title' => $section['title'],
                'content' => $section['content'],
                'sort_order' => $index + 1,
                'is_required' => isset($section['required']) ? $section['required'] : 0,
                'is_active' => 1,
                'category' => isset($section['category']) ? $section['category'] : 'terms',
            ));
        }
    }

    /**
     * Get default contract sections
     * Based on service-contract-template-v3.docx
     */
    public static function get_default_contract_sections() {
        return array(
            // Section 1: Acceptances
            array(
                'key' => 'acceptances',
                'title' => 'Acceptances',
                'category' => 'terms',
                'required' => 1,
                'content' => 'The undersigned representative of Client has the authority to enter into this Agreement on behalf of Client. Client agrees to cooperate and to provide Freelancer with everything needed to complete the Services as, when and in the format requested by Freelancer.

Freelancer has the experience and ability to do everything Freelancer agreed to for Client and will do it all in a professional and timely manner. Freelancer will endeavor to meet every deadline that\'s set and to meet the expectation for Services to the best of its abilities.',
            ),

            // Section 2: Warranty
            array(
                'key' => 'warranty',
                'title' => 'Warranty',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Freelancer warrants that: (i) the Services will be performed in a professional and workmanlike manner and that none of such Services or any part of this Agreement is or will be inconsistent with any obligation Freelancer may have to others; (ii) none of the Services or Inventions or any development, use, production, distribution or exploitation thereof will infringe, misappropriate or violate any intellectual property or other right of any person or entity (including, without limitation, Freelancer); (iii) Freelancer has the full right to provide Client with the assignments and rights provided for herein; (iv) Freelancer shall comply with all applicable laws in the course of performing the Services and (v) if Freelancer\'s work requires a license, Freelancer has obtained that license and the license is in full force and effect.

EXCEPT AS SET FORTH IN THIS ATTACHMENT B, DESIGNER DISCLAIMS ANY AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, SUITABILITY, COMPLETENESS OR RESULTS TO BE DERIVED FROM THE WORK. EXCEPT AS SET FORTH HEREIN, ALL DELIVERABLES ARE DELIVERED ON AN "AS-IS" BASIS.',
            ),

            // Section 3: Confidentiality & Non-Disclosure
            array(
                'key' => 'confidentiality',
                'title' => 'Confidentiality & Non-Disclosure',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Each party shall maintain, in the strictest confidence, all Confidential Information of the other party. "Confidential Information" means all (i) nonpublic information (at the time of disclosure) disclosed by one party to the other party under this Agreement, provided such information is marked or indicated by the disclosing party to be confidential; (ii) and any information which ought reasonably be considered confidential with regard to the circumstances surrounding disclosure, whether or not such information is marked "Confidential".

In the event a party is required to disclose Confidential Information pursuant to a judicial or other governmental order, such party shall, to the maximum extent permitted by law or opinion of counsel, provide the other party with prompt notice prior to any disclosure so that the party or its client may seek other legal remedies to maintain the confidentiality of such Confidential Information.

Each party receiving Confidential Information shall be responsible for any breach of this provision that is caused by any of its employees, affiliates, representatives or agents. The obligations of this Section shall survive termination of this Agreement for a period of 3 years.',
            ),

            // Section 4: Ownership and Licenses
            array(
                'key' => 'ownership',
                'title' => 'Ownership and Licenses',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Freelancer agrees that the deliverables and materials developed pursuant to this Agreement shall be treated as if the development of such deliverable constitutes a "work for hire" and shall be owned by Client upon payment of all fees due to Freelancer pursuant to this Agreement.

In the event that any such material is considered not to be a "work made for hire," Freelancer hereby assigns all ownership and other proprietary rights, title and interest in such deliverables and materials to Client, and agrees to execute such documents as Client may reasonably request, in order to assist Client in obtaining and protecting such rights.

Except as otherwise agreed to in writing, Freelancer shall have no rights to license, sell or use the deliverables or materials developed under this Agreement, or any portion thereof.

All sketches, concepts, and unused designs created during this project which are not selected by the Client will remain the property of the Freelancer.

<strong>All grants of license or transfer of ownership of any intellectual property rights under this Agreement are expressly conditioned upon receipt of payment in full.</strong>',
            ),

            // Section 4.1: Freelancer Tools
            array(
                'key' => 'freelancer_tools',
                'title' => 'Freelancer Tools',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Freelancer retains all rights, including intellectual property rights and copyrights, in pre-existing design tools, software, templates, type fonts, and methodologies developed and/or utilized by Freelancer in performing the Services ("Freelancer Tools"). Freelancer hereby grants to Client a non-exclusive, non-transferable, perpetual, worldwide license to use such Freelancer Tools solely as incorporated in the Final Deliverables for this Project. Client may not directly or indirectly decompile, reverse engineer, or otherwise disassemble or modify any Freelancer Tools.',
            ),

            // Section 5: Trademark Responsibility
            array(
                'key' => 'trademark',
                'title' => 'Trademark Responsibility',
                'category' => 'terms',
                'required' => 0,
                'content' => 'The Freelancer shall not be held responsible for any costs or legal liabilities arising from trademark infringements or other intellectual property issues related to the design materials after full payment has been made and ownership has transferred to Client. Client is responsible for conducting trademark searches before using deliverables commercially.',
            ),

            // Section 6: Client Responsibilities
            array(
                'key' => 'client_responsibilities',
                'title' => 'Client Responsibilities',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Client acknowledges that it shall be responsible for performing the following in a reasonable and timely manner:

(a) Coordination of any decision-making with parties other than the Freelancer;

(b) Provision of Client content in a form suitable for reproduction or incorporation into the Deliverables without further preparation, unless otherwise expressly provided;

(c) Final proofreading—in the event that Client has approved Deliverables but errors such as typographic errors or misspellings remain in the finished product, Client shall incur the cost of correcting such errors; and

(d) Ensuring that all information and claims comprising Client content are accurate, legal, and do not infringe the rights of any third party.',
            ),

            // Section 7: Non-Solicitation
            array(
                'key' => 'non_solicitation',
                'title' => 'Non-Solicitation',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Freelancer agrees that during the period in which it is providing Services and for one year thereafter, Freelancer will not encourage or solicit any employee, vendor, client or contractor of Client to leave Client for any reason.

During the term of this Agreement, and for a period of six (6) months after expiration or termination, Client agrees not to solicit, recruit, engage or otherwise employ or retain any employee or subcontractor of Freelancer who has been assigned to perform tasks under this Agreement. In the event such employment occurs, Client agrees that Freelancer shall be entitled to a fee equal to 25% of said person\'s starting salary with Client, or 25% of fees paid if engaged as an independent contractor, payable within 30 days of the employment or engagement start date.',
            ),

            // Section 8: Relationship of Parties
            array(
                'key' => 'relationship',
                'title' => 'Relationship of Parties',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Nothing contained in this Agreement shall be construed to create a partnership, joint or co-venture, agency, or employment relationship between Freelancer and Client. Both Parties agree that Freelancer is, and at all times during this Agreement shall remain, an independent contractor.',
            ),

            // Section 9: Non-Exclusivity
            array(
                'key' => 'non_exclusivity',
                'title' => 'Non-Exclusivity',
                'category' => 'terms',
                'required' => 0,
                'content' => 'The parties expressly acknowledge that this Agreement does not create an exclusive relationship between the parties. Client is free to engage others to perform services of the same or similar nature to those provided by Freelancer, and Freelancer shall be entitled to offer and provide design services to others, solicit other clients and otherwise advertise the services offered by Freelancer.',
            ),

            // Section 10: Term & Termination
            array(
                'key' => 'termination',
                'title' => 'Term & Termination',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Either Party may terminate this Agreement at any time, with or without cause, upon <em>[Number]</em> days written notice.

Either Party also may at any time terminate the Agreement immediately if the other party commits a breach of this Agreement and such party does not cure a breach within <em>[Number]</em> days of written notice from the non-breaching party of such breach.

If this Agreement is terminated earlier by Client without cause, Client agrees to pay Freelancer any and all sums which are due and payable for: (i) services provided as of the date of termination; and (ii) expenses already incurred, including those from documented non-cancelable commitments. Freelancer agrees to use best efforts to minimize such costs and expenses.

If the Client decides to cancel the project after ideas and proposals have been submitted, refunds for any previous payments made (including deposits) will not be provided.

Termination for any reason shall not affect the rights granted to Client by Freelancer hereunder. Upon termination, Client shall pay to Freelancer all undisputed amounts due and payable. If upon termination Client has not paid undisputed fees owed for the material, deliverables or Services provided by Freelancer as of the date of termination, Client agrees not to use any such material or the product of such Service, until Client has paid Freelancer in full.',
            ),

            // Section 11: Project Suspension
            array(
                'key' => 'suspension',
                'title' => 'Project Suspension',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Should the project need to be suspended for any reason, the Client and Freelancer shall discuss the terms and conditions for resuming the project at a later date. If a project is suspended for more than <em>[Number]</em> days, Freelancer reserves the right to treat the project as cancelled and retain all payments made to date. Upon resumption, a new timeline and potentially revised fees may be required.',
            ),

            // Section 11.1: Suspension Fee
            array(
                'key' => 'suspension_fee',
                'title' => 'Suspension Fee',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Client acknowledges that Freelancer has reserved time exclusively to perform the Services. Accordingly, in the event Client causes a delay in the Services ("Client Delay"), Client shall pay to Freelancer, in addition to fees and expenses already incurred through the date of such Client Delay, a Suspension Fee equal to Freelancer\'s hourly fees for idle time caused by the Client Delay, unless Freelancer is able to secure other work for that time for the same or greater compensation. Client acknowledges that if Freelancer accepts other work because of a Client Delay, Freelancer may adjust time to complete the Services as necessary to accommodate such other work.',
            ),

            // Section 12: Payment Terms
            array(
                'key' => 'payment_terms',
                'title' => 'Payment Terms',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Payments for each invoice delivered by Freelancer to Client are due within <em>[Number]</em> days of receipt. In case of overdue payments, Freelancer reserves the right to stop work until payment is received.

<strong>Late Payment:</strong> In the event an invoice is not paid on time, to the maximum extent allowable by law, Freelancer will charge a late payment fee of <em>[Percentage]</em> per month on any overdue and unpaid balance not in dispute.',
            ),

            // Section 13: Changes
            array(
                'key' => 'changes',
                'title' => 'Changes',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Any material changes to the Services, including work to be performed and related fees must be approved by the prior written consent of both parties. Unless otherwise provided herein, Client shall pay additional charges for all changes requested by Client which are outside the Scope of the Services on a time and materials basis, at Freelancer\'s standard hourly rate of <em>[Hourly Rate]</em> per hour, or in an amount separately agreed upon in writing in advance of the change.

Freelancer may extend or modify any delivery schedule or deadlines as may be required by such changes.',
            ),

            // Section 14: Indemnification and Limitation of Liability
            array(
                'key' => 'indemnification',
                'title' => 'Indemnification and Limitation of Liability',
                'category' => 'terms',
                'required' => 1,
                'content' => 'Freelancer agrees to indemnify, defend and hold harmless Client from any and all claims, actions, damages, and liabilities arising (i) through Freelancer\'s gross negligence; (ii) out of any claim that the materials or deliverables in fact infringe upon or violate any proprietary rights of any third party; or (iii) from a breach or alleged breach of any of Freelancer\'s representations, warranties or agreements herein.

Client agrees to indemnify, defend and hold harmless Freelancer from any and all claims, actions, damages, liabilities, costs and expenses arising in any manner caused by Client\'s (i) gross negligence; (ii) out of any claim that Client provided content in fact infringes upon or violates any proprietary rights of any third party; or (iii) from a breach or alleged breach of any of Client\'s representations, warranties or agreements herein.

Neither party may enter into any settlement agreement that affects the other party without the other party\'s prior written consent.

TO THE MAXIMUM EXTENT ALLOWABLE BY LAW, NEITHER PARTY SHALL BE LIABLE TO THE OTHER FOR ANY INCIDENTAL, CONSEQUENTIAL, INDIRECT, SPECIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF OR RELATING TO THIS AGREEMENT, EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

<strong>IN ALL CIRCUMSTANCES, THE MAXIMUM LIABILITY OF DESIGNER, ITS DIRECTORS, OFFICERS, EMPLOYEES, AGENTS AND AFFILIATES, TO CLIENT FOR DAMAGES FOR ANY AND ALL CAUSES WHATSOEVER, AND CLIENT\'S MAXIMUM REMEDY, REGARDLESS OF THE FORM OF ACTION, WHETHER IN CONTRACT, TORT OR OTHERWISE, SHALL BE LIMITED TO THE TOTAL FEES PAID BY CLIENT TO DESIGNER UNDER THIS AGREEMENT.</strong>',
            ),

            // Section 15: Right to Authorship Credit & Portfolio Usage
            array(
                'key' => 'authorship',
                'title' => 'Right to Authorship Credit & Portfolio Usage',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Both Parties agree that when asked, Client must properly identify Freelancer as the creator of the deliverables. Client does not have a proactive duty to display Freelancer\'s name together with the deliverables, but Client may not seek to mislead others that the deliverables were created by anyone other than Freelancer.

Client hereby agrees Freelancer may use the work product as part of Freelancer\'s portfolio and websites, galleries and other media solely for the purpose of showcasing Freelancer\'s work but not for any other purpose. Freelancer will not publish any confidential or non-public work without Client\'s prior written consent.',
            ),

            // Section 16: Governing Law and Dispute Resolution
            array(
                'key' => 'governing_law',
                'title' => 'Governing Law and Dispute Resolution',
                'category' => 'terms',
                'required' => 1,
                'content' => 'This Agreement and any dispute arising hereunder shall be governed by the laws of the jurisdiction of <em>[Freelancer\'s State/Country]</em>, without regard to the conflicts of law provisions thereof.

In the event of a dispute arising out of this Agreement, the parties agree to first attempt to resolve any dispute by negotiation between the parties. If they are unable to resolve the dispute within thirty (30) days, either party may commence mediation and/or binding arbitration through a mutually agreed-upon forum. The prevailing party in any dispute resolved by binding arbitration or litigation shall be entitled to recover its reasonable attorneys\' fees and costs.

For all purposes of this Agreement, the Parties consent to exclusive jurisdiction and venue in the courts located in said jurisdiction. The failure of either party to enforce its rights under this Agreement at any time for any period shall not be construed as a waiver of such rights.',
            ),

            // Section 17: Force Majeure
            array(
                'key' => 'force_majeure',
                'title' => 'Force Majeure',
                'category' => 'terms',
                'required' => 0,
                'content' => 'No failure or omission by a party in the performance of any obligation under this Agreement shall be deemed a breach of this Agreement or create any liability if such failure or omission shall arise from any cause or causes beyond the reasonable control of such party which cannot be overcome through that party\'s reasonable diligence, including but not limited to strikes, riots, war, acts of terrorism, acts of God, severe illness, invasion, fire, explosion, floods and acts of government or governmental agencies or instrumentalities.

In the event that the Freelancer becomes incapacitated or unable to continue the project due to unexpected and uncontrollable circumstances, a portion of the total project budget may be reimbursed based on work completed. If the work completed thus far can be utilized by another designer to continue the project, a refund percentage will be determined based on the work completed.

Upon occurrence of any Force Majeure Event, Freelancer shall give notice to Client of its inability to perform or of delay in completing the Services and shall propose revisions to the schedule for completion of the Services.',
            ),

            // Section 18: Notices
            array(
                'key' => 'notices',
                'title' => 'Notices',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Any notices to either Party made pursuant to this Agreement shall be made and sent (i) via US mail or a nationally recognized carrier to the other Party\'s address on file; (ii) or via e-mail to the other Party\'s designated representative. Each Party shall have an independent obligation to provide and update, as necessary, the mail and e-mail address on file for such notices. Notices sent by e-mail shall be deemed effective once sent if no error or "bounce back" has been received within twenty-four (24) hours of submission.',
            ),

            // Section 19: Appropriate Conduct
            array(
                'key' => 'conduct',
                'title' => 'Appropriate Conduct',
                'category' => 'terms',
                'required' => 0,
                'content' => 'Client and Freelancer will seek to maintain a professional relationship that is free from harassment of any kind and from any other offensive or disrespectful conduct. If at any time during the term of this Agreement Freelancer believes that he/she has been subjected to harassing behavior on the part of Client or Client\'s staff, Freelancer will notify Client immediately and demand that appropriate actions will be taken to correct the matter. In the event that the reported behavior continues following Freelancer\'s second notice, such behavior will constitute a breach of this Agreement.',
            ),

            // Section 20: Amendment
            array(
                'key' => 'amendment',
                'title' => 'Amendment',
                'category' => 'terms',
                'required' => 0,
                'content' => 'This Agreement may be amended or modified only by written agreement signed by both parties.',
            ),

            // Section 21: Miscellaneous
            array(
                'key' => 'miscellaneous',
                'title' => 'Miscellaneous',
                'category' => 'terms',
                'required' => 0,
                'content' => 'The section and subsection headings used in this Agreement are for convenience only and will not be used in interpreting this Agreement. Both parties have had the opportunity to review this Agreement and neither party will be deemed the drafter of this Agreement for the purposes of interpreting any ambiguity in this Agreement. The parties agree that this Agreement may be signed by manual or facsimile signatures and in counterparts, each of which will be deemed an original and all of which together will constitute one and the same instrument.

In the event that any provision of this Agreement will be determined to be illegal or unenforceable, that provision will be first revised to give the maximum permissible effect to its original intent or, if such revision is not permitted, that specific provision will be eliminated so that this Agreement will otherwise remain in full force and effect and enforceable.',
            ),

            // Section 22: Entire Contract
            array(
                'key' => 'entire_contract',
                'title' => 'Entire Contract',
                'category' => 'terms',
                'required' => 1,
                'content' => 'This Agreement, along with Attachment A, Attachment B, and any exhibits, schedules or attachments, between the Parties supersedes any previous arrangements, oral or written, and may not be modified in any respect except by a future written agreement signed by both Parties.',
            ),
        );
    }

    /**
     * Create forms table
     */
    private static function create_forms_table($charset_collate) {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'fs_forms';
        
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            name varchar(255) NOT NULL,
            description text,
            project_id bigint(20) DEFAULT NULL,
            client_id bigint(20) DEFAULT NULL,
            status varchar(20) DEFAULT 'draft',
            fields longtext,
            settings text,
            public_token varchar(64) NOT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            UNIQUE KEY public_token (public_token),
            KEY project_id (project_id),
            KEY client_id (client_id),
            KEY status (status)
        ) $charset_collate;";
        
        dbDelta($sql);
    }

    /**
     * Create form responses table
     */
    private static function create_form_responses_table($charset_collate) {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'fs_form_responses';
        
        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            form_id bigint(20) NOT NULL,
            client_id bigint(20) DEFAULT NULL,
            project_id bigint(20) DEFAULT NULL,
            respondent_name varchar(255),
            respondent_email varchar(255),
            response_data longtext NOT NULL,
            ip_address varchar(45),
            user_agent varchar(500),
            submitted_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY form_id (form_id),
            KEY client_id (client_id),
            KEY project_id (project_id),
            KEY submitted_at (submitted_at)
        ) $charset_collate;";
        
        dbDelta($sql);
    }

    /**
     * Create the /changelog/ WordPress page on freelancestud.io only.
     * Safe to call on every activation — only inserts once, identified by slug.
     */
    private static function maybe_create_changelog_page() {
        $host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : '';
        $host = preg_replace( '/:\d+$/', '', $host );
        if ( $host !== 'freelancestud.io' && $host !== 'www.freelancestud.io' ) {
            return;
        }

        // Check if the page already exists by slug.
        $existing = get_page_by_path( 'changelog', OBJECT, 'page' );
        if ( $existing ) {
            return;
        }

        wp_insert_post( array(
            'post_title'   => 'Changelog',
            'post_name'    => 'changelog',
            'post_content' => '[fs_changelog]',
            'post_status'  => 'publish',
            'post_type'    => 'page',
            'post_author'  => 1,
            'comment_status' => 'closed',
            'ping_status'    => 'closed',
        ) );
    }

    /**
     * Bank Transactions table (v8.4.0)
     */
    private static function create_transactions_table($charset_collate) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'fs_transactions';

        $sql = "CREATE TABLE IF NOT EXISTS $table_name (
            id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
            transaction_date date NOT NULL,
            description text NOT NULL,
            amount decimal(10,2) NOT NULL,
            type varchar(10) NOT NULL DEFAULT 'debit',
            bank_reference varchar(255) DEFAULT '',
            status varchar(20) NOT NULL DEFAULT 'unmatched',
            linked_expense_id bigint(20) UNSIGNED DEFAULT NULL,
            linked_invoice_id bigint(20) UNSIGNED DEFAULT NULL,
            category varchar(100) DEFAULT NULL,
            is_business tinyint(1) NOT NULL DEFAULT 1,
            import_batch varchar(64) NOT NULL DEFAULT '',
            source_bank varchar(100) DEFAULT '',
            raw_data longtext DEFAULT NULL,
            notes text DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY transaction_date (transaction_date),
            KEY status (status),
            KEY type (type),
            KEY import_batch (import_batch),
            KEY linked_expense_id (linked_expense_id),
            KEY linked_invoice_id (linked_invoice_id)
        ) $charset_collate;";

        dbDelta($sql);
    }
}
