<?php
/**
 * Frontend Router for public-facing pages
 *
 * @package Freelance_Studio
 */

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


class FS_Frontend_Router {

    /**
     * Initialize the frontend router
     */
    public static function init() {
        add_action('init', array(__CLASS__, 'add_rewrite_rules'));
        add_filter('query_vars', array(__CLASS__, 'add_query_vars'));
        add_action('template_redirect', array(__CLASS__, 'handle_public_pages'), 1);
        add_action('wp_ajax_nopriv_fs_sign_proposal', array(__CLASS__, 'ajax_sign_proposal'));
        add_action('wp_ajax_fs_sign_proposal', array(__CLASS__, 'ajax_sign_proposal'));
        add_action('wp_ajax_nopriv_fs_request_revision', array(__CLASS__, 'ajax_request_revision'));
        add_action('wp_ajax_fs_request_revision', array(__CLASS__, 'ajax_request_revision'));
        
        // Booking AJAX handlers (public)
        add_action('wp_ajax_nopriv_fs_get_available_slots', array(__CLASS__, 'ajax_get_available_slots'));
        add_action('wp_ajax_fs_get_available_slots', array(__CLASS__, 'ajax_get_available_slots'));
        add_action('wp_ajax_nopriv_fs_create_booking', array(__CLASS__, 'ajax_create_booking'));
        add_action('wp_ajax_fs_create_booking', array(__CLASS__, 'ajax_create_booking'));
    }

    /**
     * Add rewrite rules for clean URLs
     */
    public static function add_rewrite_rules() {
        // Booking pages: /book/ and /book/{slug}/
        add_rewrite_rule('^book/?$', 'index.php?fs_booking=1', 'top');
        add_rewrite_rule('^book/([^/]+)/?$', 'index.php?fs_booking=1&fs_meeting_type=$matches[1]', 'top');
        
        // Booking cancellation: /fs-booking/cancel/{token}/
        add_rewrite_rule('^fs-booking/cancel/([^/]+)/?$', 'index.php?fs_booking_cancel=$matches[1]', 'top');
        
        // Booking confirmation: /fs-booking/confirm/{token}/
        add_rewrite_rule('^fs-booking/confirm/([^/]+)/?$', 'index.php?fs_booking_confirm=$matches[1]', 'top');
        
        // Client file upload: /fs/upload/{token}/
        add_rewrite_rule('^fs/upload/([^/]+)/?$', 'index.php?fs_client_upload=$matches[1]', 'top');
    }

    /**
     * Add custom query vars
     */
    public static function add_query_vars($vars) {
        $vars[] = 'fs_booking';
        $vars[] = 'fs_meeting_type';
        $vars[] = 'fs_booking_cancel';
        $vars[] = 'fs_booking_confirm';
        $vars[] = 'fs_client_upload';
        return $vars;
    }

    /**
     * Handle public page requests
     */
    public static function handle_public_pages() {
        // Check for booking pages (supports both pretty permalinks via query_var and plain permalinks via $_GET)
        $fs_booking = get_query_var('fs_booking');
        if (!$fs_booking && isset($_GET['fs_booking'])) {
            $fs_booking = sanitize_text_field($_GET['fs_booking']);
        }
        if ($fs_booking) {
            $meeting_type_slug = get_query_var('fs_meeting_type');
            if (!$meeting_type_slug && isset($_GET['fs_meeting_type'])) {
                $meeting_type_slug = sanitize_text_field($_GET['fs_meeting_type']);
            }
            self::render_booking_page($meeting_type_slug);
            exit;
        }
        
        // Check for booking cancellation (supports both permalink types)
        $fs_booking_cancel = get_query_var('fs_booking_cancel');
        if (!$fs_booking_cancel && isset($_GET['fs_booking_cancel'])) {
            $fs_booking_cancel = sanitize_text_field($_GET['fs_booking_cancel']);
        }
        if ($fs_booking_cancel) {
            self::render_booking_cancellation($fs_booking_cancel);
            exit;
        }
        
        // Check for booking confirmation (supports both permalink types)
        $fs_booking_confirm = get_query_var('fs_booking_confirm');
        if (!$fs_booking_confirm && isset($_GET['fs_booking_confirm'])) {
            $fs_booking_confirm = sanitize_text_field($_GET['fs_booking_confirm']);
        }
        if ($fs_booking_confirm) {
            self::render_booking_confirmation($fs_booking_confirm);
            exit;
        }
        
        // Check for client file upload
        $fs_client_upload = get_query_var('fs_client_upload');
        if (!$fs_client_upload && isset($_GET['fs_client_upload'])) {
            $fs_client_upload = sanitize_text_field($_GET['fs_client_upload']);
        }
        if ($fs_client_upload) {
            self::render_client_upload_page($fs_client_upload);
            exit;
        }
        
        // Check for client portal
        if (isset($_GET['fs_portal'])) {
            self::render_client_portal(sanitize_text_field($_GET['fs_portal']));
            exit;
        }
        
        // Check for project portal view
        if (isset($_GET['fs_project_portal'])) {
            self::render_project_portal(sanitize_text_field($_GET['fs_project_portal']));
            exit;
        }
        
        // Check for proposal view
        if (isset($_GET['fs_proposal'])) {
            self::render_public_proposal(sanitize_text_field($_GET['fs_proposal']));
            exit;
        }
        
        // Check for contract view (Phase 3)
        if (isset($_GET['fs_contract'])) {
            self::render_public_contract(sanitize_text_field($_GET['fs_contract']));
            exit;
        }
        
        // Check for invoice view (future)
        if (isset($_GET['fs_invoice'])) {
            $token = sanitize_text_field($_GET['fs_invoice']);
            
            // Handle PDF download
            if (isset($_GET['download']) && $_GET['download'] === 'pdf') {
                self::download_public_invoice_pdf($token);
                exit;
            }
            
            self::render_public_invoice($token);
            exit;
        }
        
        // Check for public form view
        if (isset($_GET['fs_form'])) {
            self::render_public_form(sanitize_text_field($_GET['fs_form']));
            exit;
        }
    }

    /**
     * Render client portal dashboard
     */
    private static function render_client_portal($token) {
        global $wpdb;
        
        // Look up client by portal token
        $client = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}fs_clients WHERE portal_token = %s AND portal_enabled = 1",
            $token
        ));
        
        if (!$client) {
            self::render_error(__('Portal access denied or link expired', 'freelance-studio'));
            return;
        }
        
        // Update last access time in portal_tokens table
        $wpdb->update(
            $wpdb->prefix . 'fs_portal_tokens',
            array('last_accessed' => current_time('mysql')),
            array('client_id' => $client->id, 'token' => $token)
        );
        
        // Get client's projects (all projects for full history)
        $projects = FS_Project::get_by_client($client->id);
        
        // Get outstanding items (unpaid invoices, unsigned proposals)
        $outstanding_invoices = FS_Invoice::get_all(array(
            'client_id' => $client->id,
            'status' => array('sent', 'viewed', 'partial')
        ));
        
        $pending_proposals = FS_Proposal::get_all(array(
            'client_id' => $client->id,
            'status' => array('sent', 'viewed')
        ));
        
        // Get pending contracts (for outstanding count)
        $pending_contracts = FS_Contract::get_all(array(
            'client_id' => $client->id,
            'status' => array('sent', 'viewed')
        ));
        
        // Get ALL proposals for the proposals section
        $all_proposals = FS_Proposal::get_by_client($client->id);
        
        // Get ALL contracts for the contracts section
        $all_contracts = FS_Contract::get_by_client($client->id);
        
        // Get ALL invoices for the invoices section
        $all_invoices = FS_Invoice::get_by_client($client->id);
        
        // Get ALL projects for lookups (project names on invoices)
        $all_projects = FS_Project::get_by_client($client->id);
        $projects_lookup = array();
        foreach ($all_projects as $project) {
            $projects_lookup[$project->id] = $project;
        }
        
        // Get billing summary
        $billing_stats = self::get_client_billing_stats($client->id);
        
        // Get all client-visible tasks across projects
        $all_tasks = FS_Task::get_client_visible_tasks($client->id);
        
        // Get business settings
        $settings = self::get_business_settings();
        
        // Render the portal page
        self::render_public_page('portal/dashboard', array(
            'client' => $client,
            'projects' => $projects,
            'outstanding_invoices' => $outstanding_invoices,
            'pending_proposals' => $pending_proposals,
            'pending_contracts' => $pending_contracts,
            'all_proposals' => $all_proposals,
            'all_contracts' => $all_contracts,
            'all_invoices' => $all_invoices,
            'all_tasks' => $all_tasks,
            'projects_lookup' => $projects_lookup,
            'billing_stats' => $billing_stats,
            'settings' => $settings,
            'portal_token' => $token,
        ));
    }

    /**
     * Render project portal view
     */
    private static function render_project_portal($token) {
        global $wpdb;
        
        // Parse token format: client_token:project_id
        $parts = explode(':', $token);
        if (count($parts) !== 2) {
            self::render_error(__('Invalid project link', 'freelance-studio'));
            return;
        }
        
        $client_token = $parts[0];
        $project_id = intval($parts[1]);
        
        // Verify client access
        $client = $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$wpdb->prefix}fs_clients WHERE portal_token = %s AND portal_enabled = 1",
            $client_token
        ));
        
        if (!$client) {
            self::render_error(__('Portal access denied', 'freelance-studio'));
            return;
        }
        
        // Get project and verify it belongs to this client
        $project = FS_Project::get($project_id);
        
        if (!$project || $project->client_id != $client->id) {
            self::render_error(__('Project not found', 'freelance-studio'));
            return;
        }
        
        // Get project invoices
        $invoices = FS_Invoice::get_all(array(
            'project_id' => $project_id
        ));
        
        // Get project billing stats
        $billing_stats = array(
            'outstanding' => 0,
            'overdue' => 0,
            'paid' => 0
        );
        
        foreach ($invoices as $invoice) {
            if ($invoice->status === 'paid') {
                $billing_stats['paid'] += floatval($invoice->total);
            } elseif (in_array($invoice->status, array('sent', 'viewed', 'partial'))) {
                $billing_stats['outstanding'] += floatval($invoice->balance_due);
                if (strtotime($invoice->due_date) < time()) {
                    $billing_stats['overdue'] += floatval($invoice->balance_due);
                }
            }
        }
        
        // Get project files
        $files = self::get_project_files($project_id);
        
        // Get project tasks
        $tasks = self::get_project_tasks($project_id);
        
        // Calculate progress
        $progress = self::calculate_project_progress($project, $tasks);
        
        // Get business settings
        $settings = self::get_business_settings();
        
        // Render the project portal page
        self::render_public_page('portal/project', array(
            'client' => $client,
            'project' => $project,
            'invoices' => $invoices,
            'billing_stats' => $billing_stats,
            'files' => $files,
            'tasks' => $tasks,
            'progress' => $progress,
            'settings' => $settings,
            'portal_token' => $client_token,
        ));
    }

    /**
     * Get client billing stats
     */
    private static function get_client_billing_stats($client_id) {
        global $wpdb;
        
        $invoices_table = $wpdb->prefix . 'fs_invoices';
        
        // Outstanding (sent, viewed, partial - not yet fully paid)
        $outstanding = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(balance_due) FROM {$invoices_table} 
             WHERE client_id = %d AND status IN ('sent', 'viewed', 'partial')",
            $client_id
        ));
        
        // Overdue
        $overdue = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(balance_due) FROM {$invoices_table} 
             WHERE client_id = %d AND status IN ('sent', 'viewed', 'partial') AND due_date < %s",
            $client_id,
            current_time('Y-m-d')
        ));
        
        // Paid total (this year)
        $year_start = gmdate('Y-01-01');
        $paid = (float) $wpdb->get_var($wpdb->prepare(
            "SELECT SUM(amount_paid) FROM {$invoices_table} 
             WHERE client_id = %d AND paid_at >= %s",
            $client_id,
            $year_start
        ));
        
        return array(
            'outstanding' => $outstanding ?: 0,
            'overdue' => $overdue ?: 0,
            'paid' => $paid ?: 0
        );
    }

    /**
     * Get project files
     */
    private static function get_project_files($project_id) {
        // Use the new project files model to get shared files
        $shared_files = FS_Project_File::get_shared_files($project_id);
        
        // Convert to format expected by portal view
        $files = array();
        foreach ($shared_files as $file) {
            $enriched = FS_Project_File::enrich_file($file);
            if ($enriched) {
                // Include file even if URL is missing - display will handle it
                $files[] = (object) array(
                    'type' => 'file',
                    'name' => $enriched->filename ?: __('Unknown file', 'freelance-studio'),
                    'url' => $enriched->url ?: '',
                    'created_at' => $enriched->created_at,
                    'size' => $enriched->filesize_formatted ?? '',
                    'category' => $enriched->category ?? 'general',
                );
            }
        }
        
        return $files;
    }

    /**
     * Get project tasks
     */
    private static function get_project_tasks($project_id) {
        global $wpdb;
        
        $table = $wpdb->prefix . 'fs_tasks';
        
        return $wpdb->get_results($wpdb->prepare(
            "SELECT * FROM {$table} WHERE project_id = %d ORDER BY position ASC, due_date ASC",
            $project_id
        ));
    }

    /**
     * Calculate project progress
     */
    private static function calculate_project_progress($project, $tasks) {
        if (empty($tasks)) {
            // If no tasks, estimate based on project status
            switch ($project->status) {
                case 'pending': return 0;
                case 'active': return 50;
                case 'completed': return 100;
                default: return 0;
            }
        }
        
        $total = count($tasks);
        $completed = 0;
        
        foreach ($tasks as $task) {
            if ($task->status === 'completed') {
                $completed++;
            }
        }
        
        return $total > 0 ? round(($completed / $total) * 100) : 0;
    }

    /**
     * Render public proposal page
     */
    private static function render_public_proposal($token) {
        $proposal = FS_Proposal::get_by_token($token);
        
        if (!$proposal) {
            self::render_error(__('Proposal not found', 'freelance-studio'));
            return;
        }
        
        // Mark as viewed if status is 'sent' (triggers on first view and on re-send)
        if ($proposal->status === 'sent') {
            FS_Proposal::update_status($proposal->id, 'viewed');
            
            // Update viewed_at timestamp
            global $wpdb;
            $wpdb->update(
                $wpdb->prefix . 'fs_proposals',
                array('viewed_at' => current_time('mysql')),
                array('id' => $proposal->id)
            );
            $proposal->viewed_at = current_time('mysql');
            $proposal->status = 'viewed';
            
            // Send view notification to freelancer
            FS_Email_Service::send_proposal_viewed_notification($proposal->id);
        }
        
        // Get client and business info
        $client = FS_Client::get($proposal->client_id);
        $settings = self::get_business_settings();
        $frequencies = FS_Proposal::get_billing_frequencies();
        
        // Check if expired
        $is_expired = FS_Proposal::is_expired($proposal);
        $can_sign = in_array($proposal->status, array('sent', 'viewed', 'revised')) && !$is_expired;
        
        // Calculate deposit
        $deposit_amount = FS_Proposal::calculate_deposit($proposal);
        
        // Get portal URL if client has portal enabled
        $portal_url = '';
        if ($client && $client->portal_enabled && !empty($client->portal_token)) {
            $portal_url = home_url('?fs_portal=' . $client->portal_token);
        }
        
        // Render the page
        self::render_public_page('proposal', array(
            'proposal' => $proposal,
            'client' => $client,
            'settings' => $settings,
            'frequencies' => $frequencies,
            'is_expired' => $is_expired,
            'can_sign' => $can_sign,
            'deposit_amount' => $deposit_amount,
            'portal_url' => $portal_url,
        ));
    }

    /**
     * Render public contract page (placeholder for Phase 3)
     */
    private static function render_public_contract($token) {
        $contract = FS_Contract::get_by_token($token);
        
        if (!$contract) {
            self::render_error(__('Contract not found', 'freelance-studio'));
            return;
        }
        
        // Mark as viewed if status is 'sent' (triggers on first view and on re-send)
        if ($contract->status === 'sent') {
            FS_Contract::update_status($contract->id, 'viewed');
            $contract->status = 'viewed';
            
            // Update viewed_at timestamp
            global $wpdb;
            $wpdb->update(
                $wpdb->prefix . 'fs_contracts',
                array('viewed_at' => current_time('mysql')),
                array('id' => $contract->id)
            );
            
            // Send view notification to freelancer
            FS_Email_Service::send_contract_viewed_notification($contract->id);
        }
        
        // Get client and business info
        $client = FS_Client::get($contract->client_id);
        $settings = self::get_business_settings();
        $frequencies = FS_Contract::get_billing_frequencies();
        
        // Check if expired
        $is_expired = FS_Contract::is_expired($contract);
        $can_sign = in_array($contract->status, array('sent', 'viewed')) && !$is_expired;
        
        // Calculate deposit
        $deposit_amount = FS_Contract::calculate_deposit($contract);
        
        // Get portal URL if client has portal enabled
        $portal_url = '';
        if ($client && $client->portal_enabled && !empty($client->portal_token)) {
            $portal_url = home_url('?fs_portal=' . $client->portal_token);
        }
        
        // Render the page
        self::render_public_page('contract', array(
            'contract' => $contract,
            'client' => $client,
            'settings' => $settings,
            'frequencies' => $frequencies,
            'is_expired' => $is_expired,
            'can_sign' => $can_sign,
            'deposit_amount' => $deposit_amount,
            'portal_url' => $portal_url,
        ));
    }

    /**
     * Render public invoice page
     */
    /**
     * Download public invoice as PDF
     */
    private static function download_public_invoice_pdf($token) {
        $invoice = FS_Invoice::get_by_token($token);
        
        if (!$invoice) {
            self::render_error(__('Invoice not found', 'freelance-studio'));
            return;
        }
        
        $client = FS_Client::get($invoice->client_id);
        $project = $invoice->project_id ? FS_Project::get($invoice->project_id) : null;
        
        FS_PDF_Service::generate_invoice($invoice, $client, $project);
        exit;
    }

    private static function render_public_invoice($token) {
        $invoice = FS_Invoice::get_by_token($token);
        
        if (!$invoice) {
            self::render_error(__('Invoice not found', 'freelance-studio'));
            return;
        }
        
        // Mark as viewed if sent
        if ($invoice->status === 'sent') {
            FS_Invoice::update_status($invoice->id, 'viewed');
            $invoice->status = 'viewed';
            
            // Send first-view notification to freelancer
            FS_Email_Service::send_invoice_viewed_notification($invoice->id);
        }
        
        // Get client and business info
        $client = FS_Client::get($invoice->client_id);
        $settings = self::get_business_settings();
        
        // Get project if exists
        $project = null;
        if ($invoice->project_id) {
            $project = FS_Project::get($invoice->project_id);
        }
        
        // Parse line items
        $line_items = array();
        if (!empty($invoice->items)) {
            $line_items = is_string($invoice->items) ? json_decode($invoice->items, true) : $invoice->items;
        }
        if (!is_array($line_items)) {
            $line_items = array();
        }
        
        // Calculate status info
        $is_overdue = strtotime($invoice->due_date) < time() && !in_array($invoice->status, array('paid', 'cancelled'));
        $is_paid = $invoice->status === 'paid';
        
        // Get portal URL if client has portal enabled
        $portal_url = '';
        if ($client && $client->portal_enabled && !empty($client->portal_token)) {
            $portal_url = home_url('?fs_portal=' . $client->portal_token);
        }
        
        // Get payment link (check both option keys for backward compatibility)
        $payment_url = get_option('fs_payment_url', '');
        if (empty($payment_url)) {
            $payment_url = get_option('fs_payment_link', '');
            // Migrate to the correct key if found
            if (!empty($payment_url)) {
                update_option('fs_payment_url', $payment_url);
                delete_option('fs_payment_link');
            }
        }
        
        // Render the page
        self::render_public_page('invoice', array(
            'invoice' => $invoice,
            'client' => $client,
            'project' => $project,
            'settings' => $settings,
            'line_items' => $line_items,
            'is_overdue' => $is_overdue,
            'is_paid' => $is_paid,
            'portal_url' => $portal_url,
            'payment_url' => $payment_url,
        ));
    }

    /**
     * AJAX handler for signing proposal
     */
    public static function ajax_sign_proposal() {
        // Get client IP for rate limiting
        $ip = FS_Security_Service::get_client_ip();
        
        // Rate limit: 10 attempts per 5 minutes per IP
        if (!FS_Security_Service::check_rate_limit('sign_proposal', $ip, 10, 300)) {
            FS_Security_Service::log_security_event(
                'rate_limit_exceeded',
                'Proposal signing rate limit exceeded',
                array('ip' => $ip)
            );
            wp_send_json_error(array('message' => __('Too many attempts. Please wait a few minutes and try again.', 'freelance-studio')));
        }
        
        // Verify nonce
        if (!isset($_POST['fs_sign_nonce']) || !wp_verify_nonce($_POST['fs_sign_nonce'], 'fs_sign_proposal')) {
            wp_send_json_error(array('message' => __('Security check failed. Please refresh the page and try again.', 'freelance-studio')));
        }
        
        // Support both field names for backward compatibility
        $token = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '';
        $proposal_id = isset($_POST['proposal_id']) ? intval($_POST['proposal_id']) : 0;
        $signature = isset($_POST['signature_data']) ? $_POST['signature_data'] : (isset($_POST['signature']) ? $_POST['signature'] : '');
        $signer_name = isset($_POST['signer_name']) ? sanitize_text_field(wp_unslash($_POST['signer_name'])) : '';
        
        if ((empty($token) && empty($proposal_id)) || empty($signature) || empty($signer_name)) {
            wp_send_json_error(array('message' => __('Please provide your signature and name', 'freelance-studio')));
        }
        
        // Validate and sanitize signature using security service
        $sanitized_signature = FS_Security_Service::sanitize_signature($signature);
        if ($sanitized_signature === false) {
            FS_Security_Service::log_security_event(
                'invalid_signature',
                'Invalid signature format submitted',
                array('ip' => $ip, 'token' => substr($token, 0, 8) . '...')
            );
            wp_send_json_error(array('message' => __('Invalid signature format', 'freelance-studio')));
        }
        
        // Look up proposal by token or ID
        $proposal = null;
        if (!empty($token)) {
            $proposal = FS_Proposal::get_by_token($token);
        } elseif ($proposal_id > 0) {
            $proposal = FS_Proposal::get($proposal_id);
        }
        
        if (!$proposal) {
            FS_Security_Service::log_security_event(
                'invalid_token',
                'Invalid proposal token used',
                array('ip' => $ip, 'token' => substr($token, 0, 8) . '...')
            );
            wp_send_json_error(array('message' => __('Proposal not found', 'freelance-studio')));
        }
        
        // Check if can be signed
        if (!in_array($proposal->status, array('sent', 'viewed', 'revision_requested', 'revised'))) {
            wp_send_json_error(array('message' => __('This proposal cannot be signed', 'freelance-studio')));
        }
        
        if (FS_Proposal::is_expired($proposal)) {
            wp_send_json_error(array('message' => __('This proposal has expired', 'freelance-studio')));
        }
        
        // Record the signature (use sanitized version)
        $result = FS_Proposal::record_signature($proposal->id, $sanitized_signature, $signer_name, $ip);
        
        if ($result) {
            // Reset rate limit on success
            FS_Security_Service::reset_rate_limit('sign_proposal', $ip);
            
            // Send notification email to freelancer
            FS_Email_Service::send_proposal_approved_notification($proposal->id);
            
            wp_send_json_success(array(
                'message' => __('Proposal approved successfully! Thank you.', 'freelance-studio'),
                'redirect' => add_query_arg('signed', '1', home_url('?fs_proposal=' . $token))
            ));
        } else {
            wp_send_json_error(array('message' => __('Failed to save signature. Please try again.', 'freelance-studio')));
        }
    }

    /**
     * AJAX handler for requesting revisions
     */
    public static function ajax_request_revision() {
        // Verify nonce
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'fs_public_proposal')) {
            wp_send_json_error(array('message' => __('Security check failed', 'freelance-studio')));
        }
        
        $token = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '';
        $revision_notes = isset($_POST['revision_notes']) ? sanitize_textarea_field(wp_unslash($_POST['revision_notes'])) : '';
        
        if (empty($token)) {
            wp_send_json_error(array('message' => __('Invalid request', 'freelance-studio')));
        }
        
        if (empty($revision_notes)) {
            wp_send_json_error(array('message' => __('Please describe the changes you would like', 'freelance-studio')));
        }
        
        $proposal = FS_Proposal::get_by_token($token);
        
        if (!$proposal) {
            wp_send_json_error(array('message' => __('Proposal not found', 'freelance-studio')));
        }
        
        // Check if can request revision
        if (!in_array($proposal->status, array('sent', 'viewed'))) {
            wp_send_json_error(array('message' => __('Revisions can only be requested on active proposals', 'freelance-studio')));
        }
        
        if (FS_Proposal::is_expired($proposal)) {
            wp_send_json_error(array('message' => __('This proposal has expired', 'freelance-studio')));
        }
        
        // Record the revision request
        $result = FS_Proposal::request_revision($proposal->id, $revision_notes);
        
        if ($result) {
            // Send notification email to freelancer
            FS_Email_Service::send_revision_request_notification($proposal->id, $revision_notes);
            
            wp_send_json_success(array(
                'message' => __('Your revision request has been submitted. We will review and update the proposal.', 'freelance-studio'),
                'redirect' => add_query_arg('revision_requested', '1', home_url('?fs_proposal=' . $token))
            ));
        } else {
            wp_send_json_error(array('message' => __('Failed to submit revision request. Please try again.', 'freelance-studio')));
        }
    }

    /**
     * Render a public page template
     */
    private static function render_public_page($template, $data = array()) {
        // Extract data for use in template
        extract($data);
        
        // Get template file
        $template_file = JEEB_FS_PLUGIN_DIR . 'includes/views/public/' . $template . '.php';
        
        if (!file_exists($template_file)) {
            self::render_error(__('Template not found', 'freelance-studio'));
            return;
        }
        
        include $template_file;
    }

    /**
     * Render error page
     */
    private static function render_error($message) {
        $settings = self::get_business_settings();
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/error.php';
    }

    /**
     * Check if the current site is freelancestud.io
     * Used to restrict features that should only run on the plugin's own site.
     */
    private static function is_freelancestudio_site() {
        $host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( $_SERVER['HTTP_HOST'] ) : '';
        // Strip port if present
        $host = preg_replace( '/:\d+$/', '', $host );
        return ( $host === 'freelancestud.io' || $host === 'www.freelancestud.io' );
    }

    /**
     * Parse CHANGELOG.md into a structured array of versions.
     * Public so the [fs_changelog] shortcode can call it.
     *
     * Versions marked with <!-- @hidden --> on the heading line are excluded
     * from the public output but remain in the raw file for internal records.
     *
     * @param bool $include_hidden If true, return all versions including hidden ones.
     * @return array
     */
    public static function parse_changelog( $include_hidden = false ) {
        $changelog_file = JEEB_FS_PLUGIN_DIR . 'CHANGELOG.md';
        $versions = array();

        if ( ! file_exists( $changelog_file ) ) {
            return $versions;
        }

        $raw    = file_get_contents( $changelog_file );
        $blocks = preg_split( '/^## (Version [^\n]+)/m', $raw, -1, PREG_SPLIT_DELIM_CAPTURE );

        for ( $i = 1; $i < count( $blocks ) - 1; $i += 2 ) {
            $version_label = trim( $blocks[ $i ] );
            $body          = trim( $blocks[ $i + 1 ] );

            // Check for <!-- @hidden --> marker and strip it from the label
            $is_hidden = false;
            if ( strpos( $version_label, '<!-- @hidden -->' ) !== false ) {
                $is_hidden = true;
                $version_label = trim( str_replace( '<!-- @hidden -->', '', $version_label ) );
            }

            // Skip hidden versions in public output
            if ( $is_hidden && ! $include_hidden ) {
                continue;
            }

            $lines         = explode( "\n", $body );
            $sections      = array();
            $current_section = null;
            $loose_items   = array();

            foreach ( $lines as $line ) {
                if ( preg_match( '/^### (.+)/', $line, $m ) ) {
                    if ( ! empty( $loose_items ) ) {
                        $sections[]  = array( 'title' => '', 'items' => $loose_items );
                        $loose_items = array();
                    }
                    $current_section = array( 'title' => trim( $m[1] ), 'items' => array() );
                } elseif ( preg_match( '/^- (.+)/', $line, $m ) ) {
                    $item = trim( $m[1] );
                    if ( $current_section !== null ) {
                        $current_section['items'][] = $item;
                    } else {
                        $loose_items[] = $item;
                    }
                } elseif ( $line === '' && $current_section !== null ) {
                    $sections[]      = $current_section;
                    $current_section = null;
                }
            }
            if ( $current_section !== null ) {
                $sections[] = $current_section;
            }
            if ( ! empty( $loose_items ) ) {
                $sections[] = array( 'title' => '', 'items' => $loose_items );
            }

            $all_items = array();
            foreach ( $sections as $sec ) {
                foreach ( $sec['items'] as $it ) {
                    $all_items[] = array( 'text' => $it, 'section' => $sec['title'] );
                }
            }

            if ( ! empty( $all_items ) ) {
                $versions[] = array(
                    'label'    => $version_label,
                    'sections' => $sections,
                    'items'    => $all_items,
                );
            }
        }

        return $versions;
    }

    /**
     * Render public changelog page (virtual-page fallback, freelancestud.io only).
     * Now delegates parsing to parse_changelog().
     */
    private static function render_changelog_page() {
        $versions = self::parse_changelog();

        // Thrive Theme Builder requires a valid WP post context to render its
        // header/footer templates. Set up a minimal fake post so the global
        // $post object and loop functions behave normally.
        global $post, $wp_query;
        $fake = new WP_Post( (object) array(
            'ID'                    => 0,
            'post_title'            => 'Changelog',
            'post_name'             => 'changelog',
            'post_type'             => 'page',
            'post_status'           => 'publish',
            'comment_status'        => 'closed',
            'ping_status'           => 'closed',
            'post_content'          => '',
            'post_excerpt'          => '',
            'post_author'           => 0,
            'post_date'             => current_time('mysql'),
            'post_date_gmt'         => current_time('mysql', 1),
            'post_modified'         => current_time('mysql'),
            'post_modified_gmt'     => current_time('mysql', 1),
            'post_parent'           => 0,
            'menu_order'            => 0,
            'guid'                  => home_url('/changelog/'),
            'filter'                => 'raw',
        ) );
        $post = $fake;
        $wp_query->post        = $fake;
        $wp_query->posts       = array( $fake );
        $wp_query->post_count  = 1;
        $wp_query->found_posts = 1;
        $wp_query->is_page     = true;
        $wp_query->is_singular = true;
        $wp_query->is_404      = false;
        setup_postdata( $fake );

        get_header();
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/changelog.php';
        wp_reset_postdata();
        get_footer();
        exit;
    }

    /**
     * Get business settings
     */
    private static function get_business_settings() {
        return array(
            'business_name' => get_option('fs_business_name', ''),
            'business_email' => get_option('fs_business_email', ''),
            'business_phone' => get_option('fs_business_phone', ''),
            'business_website' => get_option('fs_business_website', ''),
            'business_address' => get_option('fs_business_address', ''),
            'business_city' => get_option('fs_business_city', ''),
            'business_state' => get_option('fs_business_state', ''),
            'business_zip' => get_option('fs_business_zip', ''),
            'business_country' => get_option('fs_business_country', ''),
            'business_logo' => get_option('fs_business_logo', ''),
            'business_logo_light' => get_option('fs_business_logo_light', ''),
            'invoice_primary_color' => get_option('fs_invoice_primary_color', '#2271b1'),
            'invoice_primary_color_mode' => get_option('fs_invoice_primary_color_mode', 'solid'),
            'invoice_primary_color_2' => get_option('fs_invoice_primary_color_2', '#1a5276'),
            'invoice_gradient_direction' => get_option('fs_invoice_gradient_direction', '135'),
            'booking_type' => get_option('fs_booking_type', 'external'),
            'booking_url' => get_option('fs_booking_url', ''),
            'contractor_signature' => get_option('fs_contractor_signature', ''),
            'contractor_signer_name' => get_option('fs_contractor_signer_name', ''),
            'contractor_signer_title' => get_option('fs_contractor_signer_title', ''),
        );
    }

    /**
     * Get client IP address
     */
    private static function get_client_ip() {
        $ip = '';
        
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        
        return sanitize_text_field($ip);
    }

    /**
     * Render public form
     */
    private static function render_public_form($token) {
        $form = FS_Form::get_by_token($token);
        
        if (!$form) {
            self::render_error(__('Form not found or link expired.', 'freelance-studio'));
            return;
        }
        
        $business = self::get_business_settings();
        $accepts_responses = FS_Form::accepts_responses($form);
        $submitted = false;
        $error = '';
        
        // Get client/project context if available
        $client_id = isset($_GET['client']) ? intval($_GET['client']) : ($form->client_id ?: 0);
        $project_id = isset($_GET['project']) ? intval($_GET['project']) : ($form->project_id ?: 0);
        
        // Handle form submission
        if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['fs_form_nonce'])) {
            if (!wp_verify_nonce($_POST['fs_form_nonce'], 'fs_submit_form_' . $form->id)) {
                $error = __('Security check failed. Please try again.', 'freelance-studio');
            } elseif (!$accepts_responses) {
                $error = __('This form is no longer accepting responses.', 'freelance-studio');
            } else {
                // Process form submission
                $response_data = array();
                $respondent_name = '';
                $respondent_email = '';
                
                foreach ($form->fields as $field) {
                    if (in_array($field['type'], array('heading', 'paragraph', 'divider', 'file_download'))) {
                        continue;
                    }
                    
                    $field_id = $field['id'];
                    $value = null;
                    
                    if ($field['type'] === 'file_upload' && !empty($_FILES['fields']['name'][$field_id])) {
                        // Handle file upload
                        $file = array(
                            'name' => $_FILES['fields']['name'][$field_id],
                            'type' => $_FILES['fields']['type'][$field_id],
                            'tmp_name' => $_FILES['fields']['tmp_name'][$field_id],
                            'error' => $_FILES['fields']['error'][$field_id],
                            'size' => $_FILES['fields']['size'][$field_id],
                        );
                        
                        if ($file['error'] === UPLOAD_ERR_OK) {
                            // Validate file type
                            $allowed_types = explode(',', $field['allowed_types'] ?? 'pdf,doc,docx,jpg,png');
                            $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
                            
                            if (in_array($ext, array_map('trim', $allowed_types))) {
                                // Validate file size
                                $max_size = intval($field['max_size'] ?? 10) * 1024 * 1024;
                                
                                if ($file['size'] <= $max_size) {
                                    require_once(ABSPATH . 'wp-admin/includes/file.php');
                                    require_once(ABSPATH . 'wp-admin/includes/media.php');
                                    require_once(ABSPATH . 'wp-admin/includes/image.php');
                                    
                                    $_FILES['form_upload'] = $file;
                                    $attachment_id = media_handle_upload('form_upload', 0);
                                    
                                    if (!is_wp_error($attachment_id)) {
                                        $value = array(
                                            'attachment_id' => $attachment_id,
                                            'url' => wp_get_attachment_url($attachment_id),
                                            'filename' => $file['name'],
                                        );
                                    }
                                }
                            }
                        }
                    } elseif ($field['type'] === 'multiple_choice') {
                        $value = isset($_POST['fields'][$field_id]) ? array_map('sanitize_text_field', $_POST['fields'][$field_id]) : array();
                    } elseif ($field['type'] === 'signature') {
                        $value = isset($_POST['fields'][$field_id]) ? sanitize_text_field(wp_unslash($_POST['fields'])[$field_id]) : '';
                    } else {
                        $value = isset($_POST['fields'][$field_id]) ? sanitize_text_field(wp_unslash($_POST['fields'])[$field_id]) : '';
                    }
                    
                    $response_data[$field_id] = $value;
                    
                    // Extract name/email from specific field types for respondent info
                    if ($field['type'] === 'email' && empty($respondent_email) && !empty($value)) {
                        $respondent_email = $value;
                    }
                    if (in_array($field['type'], array('short_text')) && empty($respondent_name) && !empty($value) && stripos($field['label'], 'name') !== false) {
                        $respondent_name = $value;
                    }
                    
                    // Check required fields
                    if (!empty($field['required']) && empty($value)) {
                        /* translators: 1. is required. value */
                        $error = sprintf(__('%s is required.', 'freelance-studio'), $field['label']);
                        break;
                    }
                }
                
                if (empty($error)) {
                    // Save response
                    $response_id = FS_Form_Response::create(array(
                        'form_id' => $form->id,
                        'client_id' => $client_id ?: null,
                        'project_id' => $project_id ?: null,
                        'respondent_name' => $respondent_name,
                        'respondent_email' => $respondent_email,
                        'response_data' => $response_data,
                        'ip_address' => self::get_client_ip(),
                        'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '',
                    ));
                    
                    if ($response_id) {
                        $submitted = true;
                        
                        // Always send notification email to admin
                        self::send_form_notification($form, $response_id, $response_data, $respondent_name, $respondent_email);
                    } else {
                        $error = __('Failed to save response. Please try again.', 'freelance-studio');
                    }
                }
            }
        }
        
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/form.php';
    }

    /**
     * Send form submission notification email
     */
    private static function send_form_notification($form, $response_id, $response_data, $respondent_name = '', $respondent_email = '') {
        $to = get_option('admin_email');
        /* translators: 1. date/time */
        $subject = sprintf(__('New Form Response: %s', 'freelance-studio'), $form->name);
        
        // Build content HTML
        $content = '';
        
        if ($respondent_name || $respondent_email) {
            $content .= '<p style="margin: 0 0 16px 0;"><strong>' . __('Respondent:', 'freelance-studio') . '</strong><br>';
            if ($respondent_name) {
                $content .= esc_html($respondent_name) . '<br>';
            }
            if ($respondent_email) {
                $content .= '<a href="mailto:' . esc_attr($respondent_email) . '" style="color: #4173B0;">' . esc_html($respondent_email) . '</a>';
            }
            $content .= '</p>';
        }
        
        $content .= '<p style="margin: 0 0 24px 0; color: #6b7280; font-size: 14px;">';
        /* translators: 1. response number, 2. submission date */
        $content .= sprintf(__('Response #%1$d • Submitted %2$s', 'freelance-studio'), $response_id, date_i18n(get_option('date_format') . ' ' . get_option('time_format')));
        $content .= '</p>';
        
        // Response data table
        $content .= '<table width="100%" cellpadding="0" cellspacing="0" style="background-color: #f9fafb; border-radius: 8px;">';
        $content .= '<tr><td style="padding: 24px;">';
        
        foreach ($form->fields as $field) {
            if (in_array($field['type'], array('heading', 'paragraph', 'divider', 'file_download'))) {
                continue;
            }
            
            $value = isset($response_data[$field['id']]) ? $response_data[$field['id']] : '';
            
            if (is_array($value)) {
                if (isset($value['url'])) {
                    $value = $value['url'];
                } else {
                    $value = implode(', ', $value);
                }
            }
            
            $content .= '<p style="margin: 0 0 16px 0; padding-bottom: 16px; border-bottom: 1px solid #e5e7eb;">';
            $content .= '<strong style="color: #374151; display: block; margin-bottom: 4px;">' . esc_html($field['label']) . '</strong>';
            $content .= '<span style="color: #111827;">' . ($value ? esc_html($value) : '<em style="color: #9ca3af;">' . __('(empty)', 'freelance-studio') . '</em>') . '</span>';
            $content .= '</p>';
        }
        
        $content .= '</td></tr></table>';
        
        FS_Email_Service::send_styled_email(
            $to, 
            $subject, 
            /* translators: 1. date/time */
            sprintf(__('New Response: %s', 'freelance-studio'), $form->name),
            $content,
            array(
                'cta_text' => __('View All Responses', 'freelance-studio'),
                'cta_url' => admin_url('admin.php?page=fs-forms&action=responses&id=' . $form->id),
                'internal' => true, // Admin notification
            )
        );
    }

    /**
     * Render public booking page
     */
    private static function render_booking_page($meeting_type_slug = '') {
        // Check if booking is enabled
        if (!get_option('fs_booking_enabled', '1')) {
            self::render_error(__('Online booking is not currently available.', 'freelance-studio'));
            return;
        }
        
        $business = self::get_business_settings();
        
        if ($meeting_type_slug) {
            // Specific meeting type page
            $meeting_type = FS_Meeting_Type::get_by_slug($meeting_type_slug);
            
            if (!$meeting_type || !$meeting_type->is_active) {
                self::render_error(__('This meeting type is not available.', 'freelance-studio'));
                return;
            }
            
            $data = array(
                'meeting_type' => $meeting_type,
                'business' => $business,
                'page_title' => $meeting_type->name . ' - ' . $business['business_name'],
            );
            
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/select-time.php';
        } else {
            // Main booking page - show all public meeting types
            $meeting_types = FS_Meeting_Type::get_bookable();
            
            if (empty($meeting_types)) {
                self::render_error(__('No meeting types are currently available for booking.', 'freelance-studio'));
                return;
            }
            
            $data = array(
                'meeting_types' => $meeting_types,
                'business' => $business,
                'page_title' => get_option('fs_booking_page_title', 'Book a Meeting'),
                'page_description' => get_option('fs_booking_page_description', ''),
            );
            
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/select-meeting.php';
        }
    }

    /**
     * Render booking cancellation page
     */
    private static function render_booking_cancellation($token) {
        $booking = FS_Booking::get_by_token($token);
        
        if (!$booking) {
            self::render_error(__('Booking not found or already cancelled.', 'freelance-studio'));
            return;
        }
        
        // Handle cancellation form submission
        if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['confirm_cancel'])) {
            $reason = isset($_POST['reason']) ? sanitize_textarea_field(wp_unslash($_POST['reason'])) : '';
            FS_Booking::cancel($booking->id, $reason, 'client');
            
            $data = array(
                'cancelled' => true,
                'booking' => $booking,
                'business' => self::get_business_settings(),
            );
            
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/cancelled.php';
            return;
        }
        
        if ($booking->status === 'cancelled') {
            $data = array(
                'cancelled' => true,
                'booking' => $booking,
                'business' => self::get_business_settings(),
            );
            
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/cancelled.php';
            return;
        }
        
        $meeting_type = FS_Meeting_Type::get($booking->meeting_type_id);
        
        $data = array(
            'booking' => $booking,
            'meeting_type' => $meeting_type,
            'business' => self::get_business_settings(),
        );
        
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/cancel.php';
    }

    /**
     * Render booking confirmation page
     */
    private static function render_booking_confirmation($token) {
        $booking = FS_Booking::get_by_token($token);
        
        if (!$booking) {
            self::render_error(__('Booking not found.', 'freelance-studio'));
            return;
        }
        
        $meeting_type = FS_Meeting_Type::get($booking->meeting_type_id);
        
        $data = array(
            'booking' => $booking,
            'meeting_type' => $meeting_type,
            'business' => self::get_business_settings(),
        );
        
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/booking/confirmation.php';
    }

    /**
     * AJAX: Get available time slots for a date
     */
    public static function ajax_get_available_slots() {
        $meeting_type_id = isset($_POST['meeting_type_id']) ? intval($_POST['meeting_type_id']) : 0;
        $date = isset($_POST['date']) ? sanitize_text_field(wp_unslash($_POST['date'])) : '';
        
        if (!$meeting_type_id || !$date) {
            wp_send_json_error(array('message' => 'Missing required parameters'));
        }
        
        $meeting_type = FS_Meeting_Type::get($meeting_type_id);
        
        if (!$meeting_type || !$meeting_type->is_active) {
            wp_send_json_error(array('message' => 'Invalid meeting type'));
        }
        
        // Check date is within allowed range
        $min_date = gmdate('Y-m-d', strtotime('+' . $meeting_type->min_notice_hours . ' hours'));
        $max_date = gmdate('Y-m-d', strtotime('+' . $meeting_type->max_days_advance . ' days'));
        
        if ($date < $min_date || $date > $max_date) {
            wp_send_json_error(array('message' => 'Date is outside booking window'));
        }
        
        // Get existing bookings for that date
        $existing_bookings = FS_Booking::get_in_range($date, $date);
        
        // Get Google Calendar events for conflict checking
        $gcal_events = array();
        if (FS_Google_Calendar_Service::is_connected()) {
            $gcal_raw = FS_Google_Calendar_Service::get_events_in_range($date, $date);
            foreach ($gcal_raw as $event) {
                // Skip all-day events for time slot conflict checking
                if ($event['all_day']) continue;
                
                $gcal_events[] = array(
                    'start' => $event['date'] . ' ' . $event['start_time'] . ':00',
                    'end' => $event['end_date'] . ' ' . $event['end_time'] . ':00',
                );
            }
        }
        
        // Get external calendar events that affect availability
        $external_events = FS_External_Calendar::get_all_events($date, $date, true);
        $external_event_count = 0;
        foreach ($external_events as $ext_event) {
            // Skip all-day events - they don't have specific times
            if (!empty($ext_event['all_day'])) continue;
            
            $gcal_events[] = array(
                'start' => $ext_event['start'],
                'end' => $ext_event['end'],
            );
            $external_event_count++;
        }
        
        // Get available slots
        $slots = FS_Availability::get_available_slots($date, $meeting_type, $existing_bookings, $gcal_events);
        
        // Get all cached events for deep debugging
        $all_calendars = FS_External_Calendar::get_all(array('is_active' => 1, 'affects_availability' => 1));
        $all_cached = array();
        foreach ($all_calendars as $cal) {
            if ($cal->cached_events) {
                $events = json_decode($cal->cached_events, true);
                if (is_array($events)) {
                    // Filter to show events near the requested date
                    foreach ($events as $evt) {
                        $evt_date = substr($evt['start'], 0, 10);
                        // Show events from day before to day after
                        if ($evt_date >= gmdate('Y-m-d', strtotime($date . ' -1 day')) && 
                            $evt_date <= gmdate('Y-m-d', strtotime($date . ' +1 day'))) {
                            $all_cached[] = $evt;
                        }
                    }
                }
            }
        }
        
        // Include debug info to help troubleshoot
        $debug = array(
            'date_requested' => $date,
            'external_calendars_checked' => count($all_calendars),
            'external_events_for_date' => count($external_events),
            'timed_events_blocking' => $external_event_count,
            'sample_events' => array_slice($external_events, 0, 5),
            'nearby_cached_events' => $all_cached,
        );
        
        wp_send_json_success(array('slots' => $slots, 'debug' => $debug));
    }

    /**
     * AJAX: Create a booking
     */
    public static function ajax_create_booking() {
        $meeting_type_id = isset($_POST['meeting_type_id']) ? intval($_POST['meeting_type_id']) : 0;
        $date = isset($_POST['date']) ? sanitize_text_field(wp_unslash($_POST['date'])) : '';
        $time = isset($_POST['time']) ? sanitize_text_field(wp_unslash($_POST['time'])) : '';
        $name = isset($_POST['name']) ? sanitize_text_field(wp_unslash($_POST['name'])) : '';
        $email = isset($_POST['email']) ? sanitize_email(wp_unslash($_POST['email'])) : '';
        $phone = isset($_POST['phone']) ? sanitize_text_field(wp_unslash($_POST['phone'])) : '';
        $notes = isset($_POST['notes']) ? sanitize_textarea_field(wp_unslash($_POST['notes'])) : '';
        $client_id = isset($_POST['client_id']) ? intval($_POST['client_id']) : 0;
        $wants_reminder = isset($_POST['wants_reminder']) ? 1 : 0;
        
        // Validate required fields
        if (!$meeting_type_id || !$date || !$time || !$name || !$email) {
            wp_send_json_error(array('message' => __('Please fill in all required fields.', 'freelance-studio')));
        }
        
        $meeting_type = FS_Meeting_Type::get($meeting_type_id);
        
        if (!$meeting_type || !$meeting_type->is_active) {
            wp_send_json_error(array('message' => __('Invalid meeting type.', 'freelance-studio')));
        }
        
        // Calculate end time
        $start_datetime = $date . ' ' . $time . ':00';
        $end_datetime = gmdate('Y-m-d H:i:s', strtotime($start_datetime . ' +' . $meeting_type->duration . ' minutes'));
        
        // Create booking
        $booking_data = array(
            'meeting_type_id' => $meeting_type_id,
            'client_id' => $client_id ?: null,
            'guest_name' => $name,
            'guest_email' => $email,
            'guest_phone' => $phone,
            'start_datetime' => $start_datetime,
            'end_datetime' => $end_datetime,
            'timezone' => get_option('fs_booking_timezone', wp_timezone_string()),
            'notes' => $notes,
            'wants_reminder' => $wants_reminder,
        );
        
        $booking_id = FS_Booking::create($booking_data);
        
        if (!$booking_id) {
            wp_send_json_error(array('message' => __('Failed to create booking. Please try again.', 'freelance-studio')));
        }
        
        $booking = FS_Booking::get($booking_id);
        
        // Send email notifications
        // 1. Confirmation email to guest (if enabled)
        if (get_option('fs_booking_confirmation_email', '1')) {
            FS_Email_Service::send_booking_confirmation($booking_id);
        }
        
        // 2. Notification email to business owner (always send)
        FS_Email_Service::send_booking_notification_to_owner($booking_id);
        
        // Generate confirmation URL based on permalink structure
        $permalink_structure = get_option('permalink_structure');
        if (empty($permalink_structure)) {
            $confirmation_url = add_query_arg('fs_booking_confirm', $booking->confirmation_token, home_url('/'));
        } else {
            $confirmation_url = home_url('/fs-booking/confirm/' . $booking->confirmation_token);
        }
        
        wp_send_json_success(array(
            'message' => __('Booking confirmed!', 'freelance-studio'),
            'booking_id' => $booking_id,
            'confirmation_url' => $confirmation_url,
        ));
    }

    /**
     * Render client file upload page
     */
    private static function render_client_upload_page($token) {
        // Validate the token
        $token_record = FS_Project_File::validate_upload_token($token);
        
        if (!$token_record) {
            // Invalid or expired token
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/upload-expired.php';
            return;
        }
        
        // Get the project
        $project = FS_Project::get($token_record->project_id);
        if (!$project) {
            include JEEB_FS_PLUGIN_DIR . 'includes/views/public/error.php';
            return;
        }
        
        // Get client info
        $client = FS_Client::get($project->client_id);
        
        // Get business info
        $business_name = get_option('fs_business_name', get_bloginfo('name'));
        $brand_color = get_option('fs_brand_color', '#1FBBA6');
        
        // Get existing files uploaded by client for this project
        $client_files = FS_Project_File::get_by_project($token_record->project_id, array(
            'category' => 'client_upload'
        ));
        
        // Enrich files
        $enriched_files = array();
        foreach ($client_files as $file) {
            $enriched_files[] = FS_Project_File::enrich_file($file);
        }
        
        include JEEB_FS_PLUGIN_DIR . 'includes/views/public/client-upload.php';
    }

    /**
     * Darken a hex color by a percentage
     */
    private static function darken_color($hex, $percent) {
        $hex = ltrim($hex, '#');
        
        $r = hexdec(substr($hex, 0, 2));
        $g = hexdec(substr($hex, 2, 2));
        $b = hexdec(substr($hex, 4, 2));
        
        $r = max(0, $r - ($r * $percent / 100));
        $g = max(0, $g - ($g * $percent / 100));
        $b = max(0, $b - ($b * $percent / 100));
        
        return sprintf('#%02x%02x%02x', $r, $g, $b);
    }
}
