<?php
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
/**
 * Daily operations, reliability, finance ledger, and lightweight workflows.
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class YATOON_Operations {
    const WORKFLOWS_OPTION = 'yatoon_workflows_v1';
    const HEALTH_OPTION = 'yatoon_health_snapshot_v1';

    public static function init() {
        self::install_schema();
        add_action( 'admin_menu', array( __CLASS__, 'add_menu' ), 22 );
        add_action( 'admin_post_yatoon_save_finance_settings', array( __CLASS__, 'save_finance_settings' ) );
        add_action( 'admin_post_yatoon_add_ledger_entry', array( __CLASS__, 'add_ledger_entry' ) );
        add_action( 'admin_post_yatoon_save_workflow', array( __CLASS__, 'save_workflow' ) );
        add_action( 'admin_post_yatoon_delete_workflow', array( __CLASS__, 'delete_workflow' ) );
        add_action( 'admin_post_yatoon_run_health_job', array( __CLASS__, 'run_health_job' ) );
        add_action( 'admin_post_yatoon_export_ledger', array( __CLASS__, 'export_ledger' ) );
        add_action( 'admin_post_yatoon_print_invoice', array( __CLASS__, 'print_invoice' ) );
        add_action( 'wp_ajax_yatoon_bulk_update_booking_status', array( __CLASS__, 'bulk_update_booking_status' ) );
        add_action( 'yatoon_booking_created', array( __CLASS__, 'booking_created' ), 80, 1 );
        add_action( 'yatoon_booking_status_changed', array( __CLASS__, 'booking_status_changed' ), 10, 3 );
        add_action( 'yatoon_payment_status_changed', array( __CLASS__, 'payment_status_changed' ), 10, 4 );
        add_action( 'yatoon_daily_health_snapshot', array( __CLASS__, 'capture_health_snapshot' ) );
        if ( ! wp_next_scheduled( 'yatoon_daily_health_snapshot' ) ) {
            wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'yatoon_daily_health_snapshot' );
        }
        if ( defined( 'WP_CLI' ) && WP_CLI ) {
            \WP_CLI::add_command( 'yatoon smoke-test', array( __CLASS__, 'cli_smoke_test' ) );
        }
    }

    public static function install_schema() {
        if ( YATOON_DB_VERSION === get_option( 'yatoon_operations_schema_version', '' ) ) {
            return;
        }
        global $wpdb;
        $table = $wpdb->prefix . 'yatoon_finance_ledger';
        $exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
        if ( $exists === $table ) {
            update_option( 'yatoon_operations_schema_version', YATOON_DB_VERSION, false );
            return;
        }
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();
        dbDelta( "CREATE TABLE {$table} (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            booking_id bigint(20) DEFAULT NULL,
            entry_type varchar(30) NOT NULL,
            amount decimal(10,2) NOT NULL DEFAULT 0,
            method varchar(40) DEFAULT '',
            reference varchar(255) DEFAULT '',
            note text DEFAULT NULL,
            created_by bigint(20) DEFAULT NULL,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY booking_id (booking_id),
            KEY entry_type (entry_type),
            KEY created_at (created_at)
        ) {$charset};" );
        if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table ) {
            update_option( 'yatoon_operations_schema_version', YATOON_DB_VERSION, false );
        }
    }

    public static function add_menu() {
        add_submenu_page(
            'yatoon-booking-system',
            __( 'Operations', 'yatoon-booking-system' ),
            __( 'Operations', 'yatoon-booking-system' ),
            'yatoon_manage_bookings',
            'yatoon-operations',
            array( __CLASS__, 'render_page' )
        );
    }

    private static function require_admin() {
        if ( ! current_user_can( 'yatoon_manage_bookings' ) ) {
            wp_die( esc_html__( 'You do not have permission to manage Yatoon operations.', 'yatoon-booking-system' ) );
        }
    }

    private static function redirect( $tab, $notice = 'saved' ) {
        wp_safe_redirect( add_query_arg(
            array( 'page' => 'yatoon-operations', 'tab' => sanitize_key( $tab ), 'yatoon_notice' => sanitize_key( $notice ) ),
            admin_url( 'admin.php' )
        ) );
        exit;
    }

    public static function save_finance_settings() {
        self::require_admin();
        check_admin_referer( 'yatoon_finance_settings' );
        update_option( 'yatoon_tax_rate', min( 100, max( 0, (float) ( $_POST['tax_rate'] ?? 0 ) ) ) );
        update_option( 'yatoon_default_service_fee', max( 0, (float) ( $_POST['service_fee'] ?? 0 ) ) );
        update_option( 'yatoon_invoice_prefix', sanitize_text_field( wp_unslash( $_POST['invoice_prefix'] ?? 'YTN' ) ) );
        self::redirect( 'finance' );
    }

    public static function add_ledger_entry() {
        self::require_admin();
        check_admin_referer( 'yatoon_add_ledger_entry' );
        $booking_id = absint( $_POST['booking_id'] ?? 0 );
        $type = sanitize_key( wp_unslash( $_POST['entry_type'] ?? '' ) );
        $allowed = array( 'payment', 'deposit', 'refund', 'adjustment', 'fee' );
        if ( ! in_array( $type, $allowed, true ) ) {
            self::redirect( 'finance', 'invalid' );
        }
        $amount = (float) ( $_POST['amount'] ?? 0 );
        if ( 'refund' === $type ) {
            $amount = -abs( $amount );
        }
        self::record_ledger(
            $booking_id,
            $type,
            $amount,
            sanitize_key( wp_unslash( $_POST['method'] ?? 'manual' ) ),
            sanitize_text_field( wp_unslash( $_POST['reference'] ?? '' ) ),
            sanitize_textarea_field( wp_unslash( $_POST['note'] ?? '' ) )
        );
        if ( $booking_id && 'refund' === $type ) {
            global $wpdb;
            $wpdb->update(
                $wpdb->prefix . 'yatoon_bookings',
                array( 'payment_status' => 'refunded', 'payment_updated_at' => current_time( 'mysql' ) ),
                array( 'id' => $booking_id ),
                array( '%s', '%s' ),
                array( '%d' )
            );
        }
        self::redirect( 'finance' );
    }

    public static function record_ledger( $booking_id, $type, $amount, $method = '', $reference = '', $note = '' ) {
        global $wpdb;
        return $wpdb->insert(
            $wpdb->prefix . 'yatoon_finance_ledger',
            array(
                'booking_id' => $booking_id ?: null,
                'entry_type' => sanitize_key( $type ),
                'amount' => round( (float) $amount, 2 ),
                'method' => sanitize_key( $method ),
                'reference' => sanitize_text_field( $reference ),
                'note' => sanitize_textarea_field( $note ),
                'created_by' => get_current_user_id() ?: null,
                'created_at' => current_time( 'mysql' ),
            ),
            array( '%d', '%s', '%f', '%s', '%s', '%s', '%d', '%s' )
        );
    }

    public static function save_workflow() {
        self::require_admin();
        check_admin_referer( 'yatoon_save_workflow' );
        $event = sanitize_key( wp_unslash( $_POST['event'] ?? '' ) );
        $action = sanitize_key( wp_unslash( $_POST['workflow_action'] ?? '' ) );
        if ( ! in_array( $event, array( 'booking_created', 'booking_status', 'payment_status' ), true )
            || ! in_array( $action, array( 'email_admin', 'email_customer', 'webhook', 'internal_note' ), true ) ) {
            self::redirect( 'automation', 'invalid' );
        }
        $rows = get_option( self::WORKFLOWS_OPTION, array() );
        $rows = is_array( $rows ) ? $rows : array();
        $rows[] = array(
            'id' => wp_generate_uuid4(),
            'name' => sanitize_text_field( wp_unslash( $_POST['name'] ?? 'Workflow' ) ),
            'event' => $event,
            'condition' => sanitize_key( wp_unslash( $_POST['condition'] ?? '' ) ),
            'action' => $action,
            'target' => sanitize_text_field( wp_unslash( $_POST['target'] ?? '' ) ),
            'subject' => sanitize_text_field( wp_unslash( $_POST['subject'] ?? 'Booking update' ) ),
            'message' => sanitize_textarea_field( wp_unslash( $_POST['message'] ?? '' ) ),
            'delay_minutes' => min( 43200, absint( $_POST['delay_minutes'] ?? 0 ) ),
            'enabled' => 1,
        );
        update_option( self::WORKFLOWS_OPTION, array_values( $rows ), false );
        self::redirect( 'automation' );
    }

    public static function delete_workflow() {
        self::require_admin();
        check_admin_referer( 'yatoon_delete_workflow' );
        $id = sanitize_text_field( wp_unslash( $_GET['workflow_id'] ?? '' ) );
        $rows = array_filter( (array) get_option( self::WORKFLOWS_OPTION, array() ), function ( $row ) use ( $id ) {
            return ( $row['id'] ?? '' ) !== $id;
        } );
        update_option( self::WORKFLOWS_OPTION, array_values( $rows ), false );
        self::redirect( 'automation' );
    }

    public static function booking_created( $booking ) {
        $booking_id = absint( $booking['booking_id'] ?? $booking['id'] ?? 0 );
        self::run_workflows( 'booking_created', '', $booking_id );
    }

    public static function booking_status_changed( $booking_id, $new_status, $old_status = '' ) {
        unset( $old_status );
        self::run_workflows( 'booking_status', sanitize_key( $new_status ), absint( $booking_id ) );
    }

    public static function payment_status_changed( $booking_id, $new_status, $old_status = '', $note = '' ) {
        if ( $new_status === $old_status ) {
            return;
        }
        global $wpdb;
        $booking = $wpdb->get_row( $wpdb->prepare(
            "SELECT total_price, deposit_amount FROM {$wpdb->prefix}yatoon_bookings WHERE id = %d",
            $booking_id
        ) );
        if ( $booking && in_array( $new_status, array( 'paid_square_pos', 'deposit_paid', 'refunded' ), true ) ) {
            $type = 'refunded' === $new_status ? 'refund' : ( 'deposit_paid' === $new_status ? 'deposit' : 'payment' );
            $amount = 'deposit' === $type && (float) $booking->deposit_amount > 0
                ? (float) $booking->deposit_amount
                : (float) $booking->total_price;
            if ( 'refund' === $type ) {
                $amount *= -1;
            }
            self::record_ledger( $booking_id, $type, $amount, 'status_update', '', $note );
        }
        self::run_workflows( 'payment_status', sanitize_key( $new_status ), absint( $booking_id ) );
    }

    private static function run_workflows( $event, $condition, $booking_id ) {
        foreach ( (array) get_option( self::WORKFLOWS_OPTION, array() ) as $workflow ) {
            if ( empty( $workflow['enabled'] ) || ( $workflow['event'] ?? '' ) !== $event ) {
                continue;
            }
            if ( ! empty( $workflow['condition'] ) && $workflow['condition'] !== $condition ) {
                continue;
            }
            if ( class_exists( 'YATOON_Job_Queue' ) ) {
                YATOON_Job_Queue::enqueue(
                    'workflow_action',
                    array(
                        'workflow_id' => $workflow['id'] ?? '',
                        'event' => $event,
                        'condition' => $condition,
                        'booking_id' => absint( $booking_id ),
                    ),
                    absint( $workflow['delay_minutes'] ?? 0 ) * MINUTE_IN_SECONDS
                );
            }
        }
    }

    public static function execute_workflow_job( array $payload ) {
        $workflow_id = sanitize_text_field( $payload['workflow_id'] ?? '' );
        $workflow = null;
        foreach ( (array) get_option( self::WORKFLOWS_OPTION, array() ) as $candidate ) {
            if ( ( $candidate['id'] ?? '' ) === $workflow_id ) {
                $workflow = $candidate;
                break;
            }
        }
        if ( ! $workflow || empty( $workflow['enabled'] ) ) {
            return;
        }
        $event = sanitize_key( $payload['event'] ?? '' );
        $condition = sanitize_key( $payload['condition'] ?? '' );
        $booking_id = absint( $payload['booking_id'] ?? 0 );
        global $wpdb;
        $booking = $booking_id ? $wpdb->get_row( $wpdb->prepare(
            "SELECT b.*, s.name service_name, st.name staff_name
             FROM {$wpdb->prefix}yatoon_bookings b
             LEFT JOIN {$wpdb->prefix}yatoon_services s ON s.id=b.service_id
             LEFT JOIN {$wpdb->prefix}yatoon_staff st ON st.id=b.staff_id
             WHERE b.id=%d",
            $booking_id
        ), ARRAY_A ) : array();
        $replace = array(
            '{{booking_id}}' => (string) $booking_id,
            '{{customer_name}}' => (string) ( $booking['customer_name'] ?? '' ),
            '{{service}}' => (string) ( $booking['service_name'] ?? '' ),
            '{{staff}}' => (string) ( $booking['staff_name'] ?? '' ),
            '{{status}}' => (string) $condition,
            '{{start_time}}' => (string) ( $booking['start_time'] ?? '' ),
            '{{total}}' => number_format( (float) ( $booking['total_price'] ?? 0 ), 2 ),
        );
        $subject = strtr( (string) ( $workflow['subject'] ?? '' ), $replace );
        $message = strtr( (string) ( $workflow['message'] ?? '' ), $replace );
        if ( 'email_admin' === $workflow['action'] ) {
            if ( ! wp_mail( get_option( 'admin_email' ), $subject, $message ) ) {
            throw new RuntimeException( __( 'Administrator email could not be queued.', 'yatoon-booking-system' ) );
            }
        } elseif ( 'email_customer' === $workflow['action'] && ! empty( $booking['customer_email'] ) ) {
            if ( ! wp_mail( $booking['customer_email'], $subject, $message ) ) {
            throw new RuntimeException( __( 'Customer email could not be queued.', 'yatoon-booking-system' ) );
            }
        } elseif ( 'webhook' === $workflow['action'] && ! empty( $workflow['target'] ) ) {
            $response = wp_remote_post( esc_url_raw( $workflow['target'] ), array(
                'timeout' => 8,
                'headers' => array( 'Content-Type' => 'application/json' ),
                'body' => wp_json_encode( array( 'event' => $event, 'condition' => $condition, 'booking' => $booking ) ),
            ) );
            if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) >= 400 ) {
            throw new RuntimeException( __( 'Webhook delivery failed.', 'yatoon-booking-system' ) );
            }
        } elseif ( 'internal_note' === $workflow['action'] && $booking_id ) {
            $existing = trim( (string) ( $booking['notes'] ?? '' ) );
            $wpdb->update(
                $wpdb->prefix . 'yatoon_bookings',
                array( 'notes' => trim( $existing . "\n" . $message ) ),
                array( 'id' => $booking_id ),
                array( '%s' ),
                array( '%d' )
            );
        }
        YATOON_Activity_Log::record( 'workflow_run', 'Automation completed: ' . ( $workflow['name'] ?? 'Workflow' ), array( 'booking_id' => $booking_id ) );
    }

    public static function health_checks() {
        global $wpdb;
        $checks = array();
        $add = function ( $key, $label, $ok, $detail ) use ( &$checks ) {
            $checks[] = compact( 'key', 'label', 'ok', 'detail' );
        };
        $add( 'services', 'Services', (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_services WHERE is_active=1" ) > 0, 'At least one active service' );
        $add( 'staff', 'Bookable team', (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_staff WHERE is_active=1 AND is_bookable=1" ) > 0, 'At least one bookable employee' );
        $add( 'reminders', 'Reminder scheduler', (bool) wp_next_scheduled( 'yatoon_send_reminders' ), 'WordPress Cron reminder event' );
        $add( 'waitlist', 'Waitlist scheduler', (bool) wp_next_scheduled( 'yatoon_check_waitlist_availability' ), 'Opening checks every 15 minutes' );

        // 4.5.0: surface the WP-Cron problem instead of letting salons discover
        // it through missed reminders.
        //
        // WP-Cron only fires when somebody loads a page. The plugin schedules
        // a one-minute job queue, a 15-minute waitlist check and a 30-minute
        // reminder run, and none of those can hold their interval on a quiet
        // salon site -- a shop whose site gets a handful of visits a day will
        // simply not send the 24h reminder on time. The opposite failure is
        // just as real: on a busy site every uncached page view pays the cost
        // of checking a due queue. A real system cron fixes both, and the only
        // reason it is not the default is that nobody was told to set it up.
        $cron_disabled = defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON;
        $alt_cron      = defined( 'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON;
        if ( $cron_disabled ) {
            $cron_detail = __( 'DISABLE_WP_CRON is set, so a real server cron is expected to call wp-cron.php. Confirm the crontab entry exists.', 'yatoon-booking-system' );
        } elseif ( $alt_cron ) {
            $cron_detail = __( 'ALTERNATE_WP_CRON is set. This is a workaround, not a scheduler; a real server cron is strongly recommended for reminders.', 'yatoon-booking-system' );
        } else {
            $cron_detail = __( 'Using WP-Cron, which only runs when someone visits the site. Reminders and waitlist checks can be late or skipped on a quiet site. Set DISABLE_WP_CRON and add a server cron calling wp-cron.php every minute.', 'yatoon-booking-system' );
        }
        $add( 'cron_mode', __( 'Scheduler mode', 'yatoon-booking-system' ), $cron_disabled, $cron_detail );

        // A scheduled event whose time is well in the past means the scheduler
        // is registered but nothing is actually executing it -- the exact
        // symptom of relying on WP-Cron with no traffic.
        $reminder_due = wp_next_scheduled( 'yatoon_send_reminders' );
        $overdue      = $reminder_due && $reminder_due < ( time() - ( 2 * HOUR_IN_SECONDS ) );
        if ( $overdue ) {
            $add(
                'cron_overdue',
                __( 'Scheduler execution', 'yatoon-booking-system' ),
                false,
                sprintf(
                    /* translators: %s: human readable time difference, e.g. "3 hours". */
                    __( 'The reminder job was due %s ago and has not run. The site is not receiving enough traffic to drive WP-Cron.', 'yatoon-booking-system' ),
                    human_time_diff( $reminder_due, time() )
                )
            );
        }
        $secret = static function ( $key ) {
            return class_exists( 'YATOON_Crypto' ) ? YATOON_Crypto::get_option( $key, '' ) : get_option( $key, '' );
        };
        $platform = sanitize_key( (string) get_option( 'yatoon_active_platform', 'local' ) );
        $sync_needed = 'square' === $platform;
        $add( 'sync', 'Booking sync', ! $sync_needed || (bool) wp_next_scheduled( 'yatoon_sync_square_bookings_only' ), $sync_needed ? 'Square booking sync event' : 'No Square sync job required for the selected platform' );
        if ( 'square' === $platform ) {
            $add( 'platform', 'Square connection', (bool) ( $secret( 'yatoon_square_access_token' ) && get_option( 'yatoon_square_location_id' ) ), 'Access token and location ID' );
            $sync_health = (array) get_option( 'yatoon_square_sync_health', array() );
            $sync_status = sanitize_key( $sync_health['status'] ?? 'unknown' );
            $sync_time = strtotime( (string) ( $sync_health['checked_at'] ?? '' ) );
            $sync_fresh = $sync_time && $sync_time >= time() - DAY_IN_SECONDS;
            $sync_ok = in_array( $sync_status, array( 'ok', 'disabled' ), true ) && ( 'disabled' === $sync_status || $sync_fresh );
            $sync_detail = $sync_time ? sprintf( 'Last checked %s ago', human_time_diff( $sync_time, time() ) ) : 'No completed sync recorded yet';
            $add( 'sync_result', 'Latest Square sync', $sync_ok, $sync_detail );
            $add( 'square_webhook_key', 'Square webhook signature', (bool) $secret( 'yatoon_square_webhook_signature_key' ), 'Required for authenticated real-time updates' );
            $webhook_health = (array) get_option( 'yatoon_square_webhook_health', array() );
            $webhook_status = sanitize_key( $webhook_health['status'] ?? 'unknown' );
            $add( 'square_webhook_event', 'Latest Square webhook', ! in_array( $webhook_status, array( 'failed', 'invalid_signature' ), true ), $webhook_health['checked_at'] ?? 'No webhook received yet' );
        } elseif ( 'vagaro' === $platform ) {
            $add( 'platform', 'Vagaro connection', class_exists( 'YATOON_Vagaro_API' ) && YATOON_Vagaro_API::is_configured(), 'Region, V2 client credentials, business ID and location timezone' );
        }
        $add( 'email', 'Business email', (bool) is_email( get_option( 'yatoon_business_email', get_option( 'admin_email' ) ) ), 'Valid notification address' );
        $payments_enabled = get_option( 'yatoon_square_payments_enabled', '0' ) === '1' || get_option( 'yatoon_deposit_enabled', '0' ) === '1';
        $payments_ready = (bool) (
            get_option( 'yatoon_square_checkout_url' ) ||
            get_option( 'yatoon_payment_request_url' ) ||
            ( $secret( 'yatoon_square_access_token' ) && get_option( 'yatoon_square_location_id' ) ) ||
            ( get_option( 'yatoon_deposit_stripe_key' ) && $secret( 'yatoon_deposit_stripe_secret' ) )
        );
        $add( 'payment', 'Payment requests', ! $payments_enabled || $payments_ready, $payments_enabled ? 'Enabled payment method has credentials' : 'Online payments are optional' );
        if ( get_option( 'yatoon_twilio_enabled', '0' ) === '1' ) {
            $twilio_ready = get_option( 'yatoon_twilio_account_sid' ) && $secret( 'yatoon_twilio_auth_token' ) && get_option( 'yatoon_twilio_from_number' );
            $add( 'twilio', 'SMS notifications', (bool) $twilio_ready, 'Twilio SID, token, and sender number' );
        }
        if ( get_option( 'yatoon_google_cal_enabled', '0' ) === '1' ) {
            $google_ready = get_option( 'yatoon_google_cal_client_id' ) && $secret( 'yatoon_google_cal_client_secret' ) && $secret( 'yatoon_google_cal_refresh_token' );
            $add( 'google_calendar', 'Google Calendar', (bool) $google_ready, 'OAuth client and connected Google account' );
        }
        $webhook_url = esc_url_raw( get_option( 'yatoon_webhook_url', '' ) );
        if ( $webhook_url ) {
            $add( 'webhook', 'Outgoing webhook', 'https' === wp_parse_url( $webhook_url, PHP_URL_SCHEME ) && (bool) wp_http_validate_url( $webhook_url ) && (bool) $secret( 'yatoon_webhook_secret' ), 'Valid HTTPS destination and HMAC signing secret' );
        }
        $migration = (array) get_option( 'yatoon_migration_status_v1', array() );
        $migration_state = sanitize_key( $migration['status'] ?? 'complete' );
        $add( 'migration', 'Database migration', ! in_array( $migration_state, array( 'backup_failed', 'restore_failed' ), true ), $migration_state ?: 'complete' );
        $add( 'https', 'Secure website', is_ssl(), 'HTTPS protects customer information' );
        return $checks;
    }

    public static function capture_health_snapshot() {
        update_option( self::HEALTH_OPTION, array( 'time' => current_time( 'mysql' ), 'checks' => self::health_checks() ), false );
    }

    public static function cli_smoke_test() {
        global $wpdb;
        $required_tables = array( 'yatoon_bookings', 'yatoon_services', 'yatoon_staff', 'yatoon_finance_ledger' );
        $failed = false;
        foreach ( $required_tables as $name ) {
            $table = $wpdb->prefix . $name;
            $exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
            $exists ? \WP_CLI::success( "Table {$name}" ) : \WP_CLI::warning( "Missing table {$name}" );
            $failed = $failed || ! $exists;
        }
        foreach ( self::health_checks() as $check ) {
            $check['ok'] ? \WP_CLI::log( "PASS {$check['label']}" ) : \WP_CLI::warning( "CHECK {$check['label']}: {$check['detail']}" );
        }
        if ( $failed ) {
            \WP_CLI::error( __( 'Yatoon database smoke test failed.', 'yatoon-booking-system' ) );
        }
        \WP_CLI::success( sprintf( __( 'Yatoon %s smoke test completed.', 'yatoon-booking-system' ), YATOON_VERSION ) );
    }

    public static function run_health_job() {
        self::require_admin();
        check_admin_referer( 'yatoon_run_health_job' );
        $job = sanitize_key( wp_unslash( $_POST['health_job'] ?? '' ) );
        if ( 'reminders' === $job ) {
            do_action( 'yatoon_send_reminders' );
        } elseif ( 'waitlist' === $job ) {
            do_action( 'yatoon_check_waitlist_availability' );
        } elseif ( 'sync' === $job ) {
            do_action( 'yatoon_sync_square_bookings_only' );
        }
        self::capture_health_snapshot();
        self::redirect( 'reliability', 'ran' );
    }

    public static function bulk_update_booking_status() {
        check_ajax_referer( 'yatoon_admin_nonce', 'nonce' );
        self::require_admin();
        $ids = array_values( array_filter( array_map( 'absint', (array) ( $_POST['booking_ids'] ?? array() ) ) ) );
        $status = sanitize_key( wp_unslash( $_POST['status'] ?? '' ) );
        if ( ! $ids || ! in_array( $status, array( 'pending', 'confirmed', 'completed', 'no_show', 'cancelled' ), true ) ) {
            wp_send_json_error( array( 'message' => __( 'Invalid bulk update.', 'yatoon-booking-system' ) ) );
        }
        global $wpdb;
        foreach ( $ids as $id ) {
            $old = (string) $wpdb->get_var( $wpdb->prepare( "SELECT status FROM {$wpdb->prefix}yatoon_bookings WHERE id=%d", $id ) );
            $wpdb->update( $wpdb->prefix . 'yatoon_bookings', array( 'status' => $status ), array( 'id' => $id ), array( '%s' ), array( '%d' ) );
            do_action( 'yatoon_booking_status_changed', $id, $status, $old );
        }
        wp_send_json_success( array( 'message' => sprintf( '%d bookings updated.', count( $ids ) ) ) );
    }

    public static function export_ledger() {
        self::require_admin();
        check_admin_referer( 'yatoon_export_ledger' );
        global $wpdb;
        $rows = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}yatoon_finance_ledger ORDER BY created_at DESC", ARRAY_A );
        nocache_headers();
        header( 'Content-Type: text/csv; charset=utf-8' );
        header( 'Content-Disposition: attachment; filename=yatoon-finance-ledger-' . gmdate( 'Y-m-d' ) . '.csv' );
        $out = fopen( 'php://output', 'w' );
        fputcsv( $out, array( 'ID', 'Booking', 'Type', 'Amount', 'Method', 'Reference', 'Note', 'Created' ) );
        foreach ( $rows as $row ) {
            fputcsv( $out, array( $row['id'], $row['booking_id'], $row['entry_type'], $row['amount'], $row['method'], $row['reference'], $row['note'], $row['created_at'] ) );
        }
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        fclose( $out );
        exit;
    }

    public static function print_invoice() {
        self::require_admin();
        check_admin_referer( 'yatoon_print_invoice' );
        global $wpdb;
        $id = absint( $_GET['booking_id'] ?? 0 );
        $booking = $wpdb->get_row( $wpdb->prepare(
            "SELECT b.*, s.name service_name, st.name staff_name FROM {$wpdb->prefix}yatoon_bookings b
             LEFT JOIN {$wpdb->prefix}yatoon_services s ON s.id=b.service_id
             LEFT JOIN {$wpdb->prefix}yatoon_staff st ON st.id=b.staff_id WHERE b.id=%d",
            $id
        ) );
        if ( ! $booking ) {
            wp_die( __( 'Booking not found.', 'yatoon-booking-system' ) );
        }
        $tax_rate = isset( $booking->tax_rate ) ? (float) $booking->tax_rate : 0.0;
        $tax      = isset( $booking->tax_amount ) ? (float) $booking->tax_amount : 0.0;
        $subtotal = isset( $booking->net_amount ) && null !== $booking->net_amount
            ? (float) $booking->net_amount
            : max( 0, (float) $booking->total_price - $tax );
        $total = (float) $booking->total_price;
        $prefix = get_option( 'yatoon_invoice_prefix', 'YTN' );
        ?>
        <!doctype html><html><head><meta charset="utf-8"><title><?php echo esc_html( $prefix . '-' . $id ); ?></title>
        <style>body{font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#172033;margin:40px}.invoice{max-width:760px;margin:auto}.head{display:flex;justify-content:space-between;border-bottom:2px solid #172033;padding-bottom:20px}.muted{color:#697386}.row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px solid #e7eaf0}.total{font-size:20px;font-weight:800}.print{margin:24px 0;padding:10px 16px}@media print{.print{display:none}}</style></head>
        <body><main class="invoice"><button class="print" onclick="window.print()">Print / Save PDF</button><div class="head"><div><h1><?php echo esc_html( get_option( 'yatoon_business_name', get_bloginfo( 'name' ) ) ); ?></h1><div class="muted"><?php echo esc_html( get_option( 'yatoon_business_address', '' ) ); ?></div></div><div><strong>Invoice <?php echo esc_html( $prefix . '-' . $id ); ?></strong><div class="muted"><?php echo esc_html( current_time( 'M j, Y' ) ); ?></div></div></div>
        <h3>Bill to</h3><p><?php echo esc_html( $booking->customer_name ); ?><br><?php echo esc_html( $booking->customer_email ); ?><br><?php echo esc_html( $booking->customer_phone ); ?></p>
        <div class="row"><span><?php echo esc_html( $booking->service_name ); ?><br><small class="muted"><?php echo esc_html( $booking->staff_name . ' · ' . $booking->start_time ); ?></small></span><strong>$<?php echo esc_html( number_format( $subtotal, 2 ) ); ?></strong></div>
        <div class="row"><span>Tax (<?php echo esc_html( number_format( $tax_rate, 2 ) ); ?>%)</span><span>$<?php echo esc_html( number_format( $tax, 2 ) ); ?></span></div>
        <div class="row total"><span>Total</span><span>$<?php echo esc_html( number_format( $total, 2 ) ); ?></span></div>
        <p class="muted">Payment status: <?php echo esc_html( ucwords( str_replace( '_', ' ', $booking->payment_status ?: 'unpaid' ) ) ); ?></p></main></body></html>
        <?php
        exit;
    }

    public static function render_page() {
        self::require_admin();
        global $wpdb;
        $tab = sanitize_key( wp_unslash( $_GET['tab'] ?? 'reliability' ) );
        $tabs = array( 'reliability' => 'Reliability', 'finance' => 'Finance', 'automation' => 'Automation' );
        if ( ! isset( $tabs[ $tab ] ) ) {
            $tab = 'reliability';
        }
        $base = admin_url( 'admin.php?page=yatoon-operations' );
        ?>
        <div class="wrap yatoon-ops"><div class="yatoon-ops-head"><div><span class="yatoon-eyebrow">Daily operations</span><h1>Operations Center</h1><p>Monitor booking health, money movement, and routine follow-up from one place.</p></div><a class="button button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=sbs-bookings' ) ); ?>">Manage bookings</a></div>
        <?php if ( ! empty( $_GET['yatoon_notice'] ) ) : ?><div class="notice notice-success is-dismissible"><p>Operations updated.</p></div><?php endif; ?>
        <nav class="nav-tab-wrapper"><?php foreach ( $tabs as $key => $label ) : ?><a class="nav-tab <?php echo $tab === $key ? 'nav-tab-active' : ''; ?>" href="<?php echo esc_url( add_query_arg( 'tab', $key, $base ) ); ?>"><?php echo esc_html( $label ); ?></a><?php endforeach; ?></nav>
        <?php
        if ( 'finance' === $tab ) {
            self::render_finance( $wpdb );
        } elseif ( 'automation' === $tab ) {
            self::render_automation();
        } else {
            self::render_reliability();
        }
        ?></div><?php
    }

    private static function render_reliability() {
        global $wpdb;
        $checks = self::health_checks();
        $passed = count( array_filter( $checks, function ( $check ) {
 return $check['ok'];
} ) );
        $sync_health = (array) get_option( 'yatoon_square_sync_health', array() );
        $webhook_health = (array) get_option( 'yatoon_square_webhook_health', array() );
        $migration = (array) get_option( 'yatoon_migration_status_v1', array() );
        $backups = (array) get_option( 'yatoon_upgrade_backups_v1', array() );
        $trend = class_exists( 'YATOON_Health_History' ) ? YATOON_Health_History::summary( 7 ) : array( 'total' => 0, 'success' => 0, 'failed' => 0, 'retry' => 0, 'average_ms' => 0 );
        $technical_summary = class_exists( 'YATOON_Health_History' ) ? YATOON_Health_History::technical_summary( $checks ) : '';
        $failed_jobs = $wpdb->get_results( "SELECT id,job_type,status,attempts,last_error,available_at FROM {$wpdb->prefix}yatoon_jobs WHERE status IN ('failed','retry') ORDER BY id DESC LIMIT 10" );
        self::render_recovery_grid( $wpdb, $failed_jobs, $migration );
        ?>
        <div class="yatoon-ops-kpis"><div><strong><?php echo esc_html( $passed . '/' . count( $checks ) ); ?></strong><span>checks passing</span></div><div><strong><?php echo esc_html( wp_next_scheduled( 'yatoon_send_reminders' ) ? human_time_diff( time(), wp_next_scheduled( 'yatoon_send_reminders' ) ) : 'Not scheduled' ); ?></strong><span>next reminder run</span></div><div><strong><?php echo esc_html( wp_timezone_string() ?: 'UTC' ); ?></strong><span>site timezone</span></div></div>
        <section class="yatoon-ops-panel"><div class="panel-title"><div><h2>System checks</h2><p>Issues here can stop appointments, reminders, or payments from working normally.</p></div><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_run_health_job' ); ?><input type="hidden" name="action" value="yatoon_run_health_job"><input type="hidden" name="health_job" value="reminders"><button class="button">Run reminders now</button></form></div>
        <div class="yatoon-check-list"><?php foreach ( $checks as $check ) : ?><div class="yatoon-check"><span class="dashicons <?php echo $check['ok'] ? 'dashicons-yes-alt ok' : 'dashicons-warning warn'; ?>"></span><div><strong><?php echo esc_html( $check['label'] ); ?></strong><small><?php echo esc_html( $check['detail'] ); ?></small></div><span class="yatoon-status"><?php echo $check['ok'] ? 'Ready' : 'Needs attention'; ?></span></div><?php endforeach; ?></div></section>
        <section class="yatoon-ops-panel"><h2>Manual jobs</h2><div class="yatoon-job-row"><?php foreach ( array( 'sync' => 'Sync bookings', 'waitlist' => 'Check waitlist', 'reminders' => 'Send due reminders' ) as $job => $label ) : ?><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_run_health_job' ); ?><input type="hidden" name="action" value="yatoon_run_health_job"><input type="hidden" name="health_job" value="<?php echo esc_attr( $job ); ?>"><button class="button"><?php echo esc_html( $label ); ?></button></form><?php endforeach; ?></div></section>
        <section class="yatoon-ops-panel yatoon-square-recovery"><div class="panel-title"><div><h2>Square recovery center</h2><p>When Square Calendar or POS does not match the website, start here.</p></div><div class="yatoon-job-row"><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_run_health_job' ); ?><input type="hidden" name="action" value="yatoon_run_health_job"><input type="hidden" name="health_job" value="sync"><button class="button button-primary">Sync Square now</button></form><a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_export_diagnostics' ), 'yatoon_export_diagnostics' ) ); ?>">Download diagnostics</a></div></div><div class="yatoon-recovery-steps"><div><strong>1. Sync</strong><span>Pull recent Square appointments into WordPress.</span></div><div><strong>2. Check</strong><span>Review the latest sync, webhook, and failed-job status below.</span></div><div><strong>3. Retry</strong><span>Retry failed jobs after Square or the network is healthy again.</span></div><div><strong>4. Export</strong><span>Send diagnostics to support before changing credentials.</span></div></div></section>
        <div class="yatoon-ops-grid">
            <section class="yatoon-ops-panel"><h2>Square sync health</h2><div class="yatoon-health-facts"><p><strong>Latest sync:</strong> <?php echo esc_html( $sync_health['status'] ?? 'Not recorded' ); ?></p><p><strong>Checked:</strong> <?php echo esc_html( $sync_health['checked_at'] ?? '-' ); ?></p><p><strong>Duration:</strong> <?php echo isset( $sync_health['duration_ms'] ) ? esc_html( $sync_health['duration_ms'] . ' ms' ) : '-'; ?></p><p><strong>Latest webhook:</strong> <?php echo esc_html( $webhook_health['status'] ?? 'Not recorded' ); ?> <?php echo ! empty( $webhook_health['checked_at'] ) ? esc_html( '(' . $webhook_health['checked_at'] . ')' ) : ''; ?></p><?php if ( ! empty( $sync_health['error'] ) || ! empty( $webhook_health['error'] ) ) : ?><div class="notice notice-error inline"><p><?php echo esc_html( $sync_health['error'] ?? $webhook_health['error'] ); ?></p></div><?php endif; ?></div></section>
            <section class="yatoon-ops-panel"><h2><?php esc_html_e( 'Upgrade protection', 'yatoon-booking-system' ); ?></h2><p><strong><?php esc_html_e( 'Migration:', 'yatoon-booking-system' ); ?></strong> <?php echo esc_html( $migration['status'] ?? __( 'No upgrade recorded', 'yatoon-booking-system' ) ); ?></p><p><strong><?php esc_html_e( 'Database:', 'yatoon-booking-system' ); ?></strong> <?php echo esc_html( get_option( 'yatoon_schema_version', '-' ) ); ?> / <?php echo esc_html( YATOON_DB_VERSION ); ?></p><div class="yatoon-job-row"><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_create_backup' ); ?><input type="hidden" name="action" value="yatoon_create_backup"><button class="button"><?php esc_html_e( 'Create database snapshot', 'yatoon-booking-system' ); ?></button></form><a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_export_diagnostics' ), 'yatoon_export_diagnostics' ) ); ?>"><?php esc_html_e( 'Download diagnostics', 'yatoon-booking-system' ); ?></a></div><?php if ( $backups ) : ?><div class="yatoon-backup-list"><?php foreach ( array_reverse( $backups, true ) as $backup_id => $backup ) : ?><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Restore this snapshot? Current Yatoon data in the protected tables will be replaced.', 'yatoon-booking-system' ) ); ?>');"><?php wp_nonce_field( 'yatoon_restore_backup' ); ?><input type="hidden" name="action" value="yatoon_restore_backup"><input type="hidden" name="backup_id" value="<?php echo esc_attr( $backup_id ); ?>"><span><?php echo esc_html( ( $backup['created'] ?? $backup_id ) . ' · ' . ( $backup['version'] ?? '-' ) ); ?></span><button class="button button-small"><?php esc_html_e( 'Restore', 'yatoon-booking-system' ); ?></button></form><?php endforeach; ?></div><?php endif; ?></section>
        </div>
        <div class="yatoon-ops-grid">
            <section class="yatoon-ops-panel"><h2>7-day integration trend</h2><div class="yatoon-trend-grid"><div><strong><?php echo absint( $trend['total'] ); ?></strong><span>events</span></div><div><strong><?php echo absint( $trend['success'] ); ?></strong><span>successful</span></div><div><strong><?php echo absint( $trend['retry'] ); ?></strong><span>retried</span></div><div><strong><?php echo absint( $trend['failed'] ); ?></strong><span>failed</span></div></div><p class="description">Average completed sync time: <?php echo absint( $trend['average_ms'] ); ?> ms. History is privacy-safe and limited to the latest 250 events.</p></section>
            <section class="yatoon-ops-panel"><div class="panel-title"><div><h2>Technical summary</h2><p>Safe to paste into a support request; credentials and customer records are excluded.</p></div><button type="button" class="button yatoon-copy-summary" data-target="yatoon-technical-summary">Copy</button></div><textarea id="yatoon-technical-summary" class="yatoon-technical-summary" rows="7" readonly><?php echo esc_textarea( $technical_summary ); ?></textarea><span class="yatoon-copy-status" role="status" aria-live="polite"></span></section>
        </div>
        <section class="yatoon-ops-panel"><div class="panel-title"><div><h2>Failed and retrying jobs</h2><p>Automatic retries remain active; use Retry when an external service has recovered.</p></div></div><div class="yatoon-table-scroll"><table class="widefat striped"><thead><tr><th>Job</th><th>Status</th><th>Attempts</th><th>Next try</th><th>Error</th><th></th></tr></thead><tbody><?php if ( ! $failed_jobs ) : ?><tr><td colspan="6" class="yatoon-empty-state">No failed background jobs.</td></tr><?php endif; ?><?php foreach ( $failed_jobs as $failed_job ) : ?><tr><td>#<?php echo absint( $failed_job->id ); ?> <?php echo esc_html( $failed_job->job_type ); ?></td><td><?php echo esc_html( $failed_job->status ); ?></td><td><?php echo absint( $failed_job->attempts ); ?></td><td><?php echo esc_html( $failed_job->available_at ); ?></td><td><?php echo esc_html( $failed_job->last_error ); ?></td><td><a class="button button-small" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_retry_job&job_id=' . absint( $failed_job->id ) ), 'yatoon_retry_job' ) ); ?>">Retry</a></td></tr><?php endforeach; ?></tbody></table></div></section>
        <section class="yatoon-ops-panel"><h2>Multi-store validation kit</h2><p>For each beta salon, export diagnostics before and after testing: fresh install or upgrade, Square connection, create/change/reschedule/cancel, POS visibility, webhook delivery, mobile booking, reminders, and rollback snapshot. Diagnostics exclude stored API secrets and customer rows.</p></section>
        <style>.yatoon-recovery-steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-top:12px}.yatoon-recovery-steps>div{padding:14px;border:1px solid #eaecf0;border-radius:10px;background:#f8fafc}.yatoon-recovery-steps strong,.yatoon-recovery-steps span{display:block}.yatoon-recovery-steps span{margin-top:5px;color:#667085;line-height:1.45}@media(max-width:782px){.yatoon-recovery-steps{grid-template-columns:1fr}}</style>
        <script>document.addEventListener('click',function(e){var b=e.target.closest('.yatoon-copy-summary');if(!b)return;var t=document.getElementById(b.getAttribute('data-target'));if(!t)return;t.select();t.setSelectionRange(0,t.value.length);var done=function(){var s=b.closest('.yatoon-ops-panel').querySelector('.yatoon-copy-status');if(s)s.textContent=<?php echo wp_json_encode( __( 'Copied to clipboard.', 'yatoon-booking-system' ) ); ?>;};if(navigator.clipboard&&window.isSecureContext){navigator.clipboard.writeText(t.value).then(done);}else{document.execCommand('copy');done();}});</script>
        <?php self::styles(); ?>
        <?php
    }

    private static function render_recovery_grid( $wpdb, $failed_jobs, $migration ) {
        $stuck_payments = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_bookings WHERE status<>'cancelled' AND payment_status IN ('pending','processing','failed') AND created_at < UTC_TIMESTAMP() - INTERVAL 15 MINUTE" );
        $failed_sync = 0;
        $failed_notifications = 0;
        foreach ( $failed_jobs as $failed_job ) {
            $job_type = strtolower( (string) $failed_job->job_type );
            if ( false !== strpos( $job_type, 'sync' ) || false !== strpos( $job_type, 'square' ) || false !== strpos( $job_type, 'calendar' ) ) {
                $failed_sync++;
            }
            if ( false !== strpos( $job_type, 'mail' ) || false !== strpos( $job_type, 'email' ) || false !== strpos( $job_type, 'sms' ) || false !== strpos( $job_type, 'notification' ) || false !== strpos( $job_type, 'reminder' ) ) {
                $failed_notifications++;
            }
        }
        $migration_ready = get_option( 'yatoon_schema_version', '' ) === YATOON_DB_VERSION && ! in_array( (string) ( $migration['status'] ?? '' ), array( 'backup_failed', 'restore_failed' ), true );
        ?>
        <section class="yatoon-ops-panel yatoon-recovery-center"><div class="panel-title"><div><h2><?php esc_html_e( 'Booking recovery center', 'yatoon-booking-system' ); ?></h2><p><?php esc_html_e( 'Recover payments, synchronization, notifications, and database upgrades without editing credentials or customer data.', 'yatoon-booking-system' ); ?></p></div></div><div class="yatoon-recovery-grid">
            <article class="<?php echo $stuck_payments ? 'needs-attention' : 'is-ready'; ?>"><span class="dashicons dashicons-money-alt"></span><div><strong><?php esc_html_e( 'Payments', 'yatoon-booking-system' ); ?></strong><p><?php echo esc_html( $stuck_payments ? sprintf( _n( '%d payment needs review.', '%d payments need review.', $stuck_payments, 'yatoon-booking-system' ), $stuck_payments ) : __( 'No stuck payment states detected.', 'yatoon-booking-system' ) ); ?></p><a href="<?php echo esc_url( admin_url( 'admin.php?page=sbs-bookings' ) ); ?>"><?php esc_html_e( 'Review bookings', 'yatoon-booking-system' ); ?></a></div></article>
            <article class="<?php echo $failed_sync ? 'needs-attention' : 'is-ready'; ?>"><span class="dashicons dashicons-update"></span><div><strong><?php esc_html_e( 'Calendar and Square sync', 'yatoon-booking-system' ); ?></strong><p><?php echo esc_html( $failed_sync ? sprintf( _n( '%d failed sync job.', '%d failed sync jobs.', $failed_sync, 'yatoon-booking-system' ), $failed_sync ) : __( 'No failed sync jobs detected.', 'yatoon-booking-system' ) ); ?></p><a href="<?php echo esc_url( admin_url( 'admin.php?page=yatoon-performance' ) ); ?>"><?php esc_html_e( 'Review and retry', 'yatoon-booking-system' ); ?></a></div></article>
            <article class="<?php echo $failed_notifications ? 'needs-attention' : 'is-ready'; ?>"><span class="dashicons dashicons-email-alt"></span><div><strong><?php esc_html_e( 'Notifications', 'yatoon-booking-system' ); ?></strong><p><?php echo esc_html( $failed_notifications ? sprintf( _n( '%d notification job needs attention.', '%d notification jobs need attention.', $failed_notifications, 'yatoon-booking-system' ), $failed_notifications ) : __( 'No failed notification jobs detected.', 'yatoon-booking-system' ) ); ?></p><a href="<?php echo esc_url( admin_url( 'admin.php?page=yatoon-booking-system&tab=email' ) ); ?>"><?php esc_html_e( 'Check notification settings', 'yatoon-booking-system' ); ?></a></div></article>
            <article class="<?php echo $migration_ready ? 'is-ready' : 'needs-attention'; ?>"><span class="dashicons dashicons-database"></span><div><strong><?php esc_html_e( 'Database and upgrades', 'yatoon-booking-system' ); ?></strong><p><?php echo esc_html( $migration_ready ? __( 'Schema is current and recoverable.', 'yatoon-booking-system' ) : __( 'Migration or restore needs attention.', 'yatoon-booking-system' ) ); ?></p><a href="<?php echo esc_url( admin_url( 'admin.php?page=yatoon-operations' ) ); ?>"><?php esc_html_e( 'Open upgrade protection', 'yatoon-booking-system' ); ?></a></div></article>
        </div></section>
        <style>.yatoon-recovery-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}.yatoon-recovery-grid article{display:flex;gap:12px;min-width:0;padding:15px;border:1px solid #dfe3ea;border-radius:10px;background:#f8fafc}.yatoon-recovery-grid article>span{flex:none}.yatoon-recovery-grid article div{min-width:0}.yatoon-recovery-grid article p{margin:5px 0 9px;color:#667085;overflow-wrap:anywhere}.yatoon-recovery-grid .is-ready>span{color:#15803d}.yatoon-recovery-grid .needs-attention{border-color:#f0b849;background:#fffaf0}.yatoon-recovery-grid .needs-attention>span{color:#b45309}@media(max-width:1000px){.yatoon-recovery-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:782px){.yatoon-recovery-grid{grid-template-columns:1fr}}</style>
        <?php
    }

    private static function render_finance( $wpdb ) {
        $stats = $wpdb->get_row( "SELECT COALESCE(SUM(total_price),0) scheduled,
            COALESCE(SUM(CASE WHEN payment_status IN ('unpaid','') OR payment_status IS NULL THEN total_price ELSE 0 END),0) outstanding,
            COALESCE(SUM(CASE WHEN payment_status IN ('paid_square_pos','deposit_paid') THEN total_price ELSE 0 END),0) recorded
            FROM {$wpdb->prefix}yatoon_bookings WHERE status <> 'cancelled'" );
        $ledger = $wpdb->get_results( "SELECT l.*, b.customer_name FROM {$wpdb->prefix}yatoon_finance_ledger l LEFT JOIN {$wpdb->prefix}yatoon_bookings b ON b.id=l.booking_id ORDER BY l.created_at DESC LIMIT 100" );
        ?>
        <div class="yatoon-ops-kpis"><div><strong>$<?php echo esc_html( number_format( (float) $stats->scheduled, 2 ) ); ?></strong><span>scheduled value</span></div><div><strong>$<?php echo esc_html( number_format( (float) $stats->outstanding, 2 ) ); ?></strong><span>outstanding</span></div><div><strong>$<?php echo esc_html( number_format( (float) $stats->recorded, 2 ) ); ?></strong><span>marked paid/deposit</span></div></div>
        <div class="yatoon-ops-grid"><section class="yatoon-ops-panel"><h2>Invoice defaults</h2><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_finance_settings' ); ?><input type="hidden" name="action" value="yatoon_save_finance_settings"><label>Invoice prefix<input name="invoice_prefix" value="<?php echo esc_attr( get_option( 'yatoon_invoice_prefix', 'YTN' ) ); ?>"></label><label>Default tax rate (%)<input type="number" step="0.01" min="0" max="100" name="tax_rate" value="<?php echo esc_attr( get_option( 'yatoon_tax_rate', 0 ) ); ?>"></label><p>Tax labels, inclusive pricing, registration details, and service overrides are managed in Settings → Tax / VAT.</p><button class="button button-primary">Save defaults</button></form></section>
        <section class="yatoon-ops-panel"><h2>Record transaction</h2><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_add_ledger_entry' ); ?><input type="hidden" name="action" value="yatoon_add_ledger_entry"><label>Booking ID<input type="number" min="1" name="booking_id"></label><label>Type<select name="entry_type"><option value="payment">Payment</option><option value="deposit">Deposit</option><option value="refund">Refund</option><option value="fee">Fee</option><option value="adjustment">Adjustment</option></select></label><label>Amount ($)<input type="number" step="0.01" name="amount" required></label><label>Method<select name="method"><option value="cash">Cash</option><option value="square">Square</option><option value="stripe">Stripe</option><option value="card">Other card</option><option value="manual">Manual</option></select></label><label>Reference<input name="reference"></label><label>Note<textarea name="note" rows="2"></textarea></label><button class="button button-primary">Add entry</button></form></section></div>
        <section class="yatoon-ops-panel"><div class="panel-title"><div><h2>Finance ledger</h2><p>Append-only record of payments, deposits, refunds, fees, and adjustments.</p></div><a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_export_ledger' ), 'yatoon_export_ledger' ) ); ?>">Export CSV</a></div><div class="yatoon-table-scroll"><table class="widefat striped"><thead><tr><th>Date</th><th>Booking</th><th>Customer</th><th>Type</th><th>Method</th><th>Reference</th><th class="num">Amount</th></tr></thead><tbody><?php if ( ! $ledger ) : ?><tr><td colspan="7">No ledger entries yet. New payment changes will appear here automatically.</td></tr><?php endif; ?><?php foreach ( $ledger as $row ) : ?><tr><td><?php echo esc_html( $row->created_at ); ?></td><td><?php echo $row->booking_id ? '#' . esc_html( $row->booking_id ) : '-'; ?></td><td><?php echo esc_html( $row->customer_name ?: '-' ); ?></td><td><?php echo esc_html( ucwords( $row->entry_type ) ); ?></td><td><?php echo esc_html( ucwords( $row->method ) ); ?></td><td><?php echo esc_html( $row->reference ); ?></td><td class="num <?php echo (float) $row->amount < 0 ? 'negative' : ''; ?>">$<?php echo esc_html( number_format( (float) $row->amount, 2 ) ); ?></td></tr><?php endforeach; ?></tbody></table></div></section>
        <?php self::styles(); ?>
        <?php
    }

    private static function render_automation() {
        $rows = (array) get_option( self::WORKFLOWS_OPTION, array() );
        ?>
        <div class="yatoon-ops-grid yatoon-auto-grid"><section class="yatoon-ops-panel"><h2>New automation</h2><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"><?php wp_nonce_field( 'yatoon_save_workflow' ); ?><input type="hidden" name="action" value="yatoon_save_workflow"><label>Name<input name="name" required placeholder="Confirmed booking follow-up"></label><label>When<select name="event"><option value="booking_created">Booking is created</option><option value="booking_status">Booking status changes</option><option value="payment_status">Payment status changes</option></select></label><label>Only when value equals<input name="condition" placeholder="Optional: confirmed, completed, refunded"></label><label>Wait before running (minutes)<input type="number" min="0" max="43200" name="delay_minutes" value="0"></label><label>Then<select name="workflow_action"><option value="email_admin">Email administrator</option><option value="email_customer">Email customer</option><option value="webhook">Send webhook</option><option value="internal_note">Add internal booking note</option></select></label><label>Webhook URL (webhook only)<input type="url" name="target"></label><label>Subject<input name="subject" value="Booking update #{{booking_id}}"></label><label>Message<textarea name="message" rows="6">Hi {{customer_name}},

Your {{service}} appointment with {{staff}} is now {{status}}.

Appointment: {{start_time}}
Total: ${{total}}</textarea></label><p class="description">Available: {{booking_id}}, {{customer_name}}, {{service}}, {{staff}}, {{status}}, {{start_time}}, {{total}}</p><button class="button button-primary">Create automation</button></form></section>
        <section class="yatoon-ops-panel"><h2>Active automations</h2><?php if ( ! $rows ) : ?><div class="yatoon-empty"><span class="dashicons dashicons-controls-repeat"></span><strong>No automations yet</strong><p>Create one focused rule first, then expand only when it saves real work.</p></div><?php endif; ?><?php foreach ( $rows as $row ) : ?><article class="yatoon-workflow"><div><strong><?php echo esc_html( $row['name'] ); ?></strong><p><?php echo esc_html( ucwords( str_replace( '_', ' ', $row['event'] ) ) ); ?> → <?php echo esc_html( ucwords( str_replace( '_', ' ', $row['action'] ) ) ); ?></p><?php if ( ! empty( $row['condition'] ) ) : ?><small>Condition: <?php echo esc_html( $row['condition'] ); ?></small><?php endif; ?><?php if ( ! empty( $row['delay_minutes'] ) ) : ?><small> · Delay: <?php echo esc_html( absint( $row['delay_minutes'] ) ); ?> min</small><?php endif; ?></div><a class="button button-small" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_delete_workflow&workflow_id=' . rawurlencode( $row['id'] ) ), 'yatoon_delete_workflow' ) ); ?>">Delete</a></article><?php endforeach; ?></section></div>
        <?php self::styles(); ?>
        <?php
    }

    private static function styles() {
        ?><style>
        .yatoon-ops{max-width:1320px}.yatoon-ops-head{display:flex;align-items:flex-start;justify-content:space-between;gap:24px;margin:22px 0}.yatoon-ops-head h1{margin:3px 0 6px;font-size:30px}.yatoon-ops-head p{margin:0;color:#667085}.yatoon-eyebrow{text-transform:uppercase;font-size:11px;font-weight:800;color:#a83262}.yatoon-ops .nav-tab-wrapper{margin-bottom:20px}.yatoon-ops-kpis{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin:18px 0}.yatoon-ops-kpis>div,.yatoon-ops-panel{background:#fff;border:1px solid #dfe3ea;border-radius:8px}.yatoon-ops-kpis>div{padding:20px}.yatoon-ops-kpis strong{display:block;font-size:25px}.yatoon-ops-kpis span{color:#667085}.yatoon-ops-panel{padding:22px;margin-bottom:16px}.yatoon-ops-panel h2{margin:0 0 8px;font-size:18px}.panel-title{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:14px}.panel-title p{margin:3px 0;color:#667085}.yatoon-check-list{border-top:1px solid #eaecf0}.yatoon-check{display:grid;grid-template-columns:28px 1fr auto;align-items:center;padding:14px 0;border-bottom:1px solid #eaecf0}.yatoon-check small{display:block;color:#667085;margin-top:2px}.yatoon-check .ok{color:#15803d}.yatoon-check .warn{color:#b45309}.yatoon-status{font-size:12px;font-weight:700}.yatoon-job-row{display:flex;flex-wrap:wrap;gap:10px}.yatoon-ops-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.yatoon-ops-panel label{display:block;font-weight:600;margin:0 0 13px}.yatoon-ops-panel input,.yatoon-ops-panel select,.yatoon-ops-panel textarea{display:block;width:100%;max-width:none;margin-top:5px}.yatoon-table-scroll{overflow:auto}.yatoon-table-scroll table{min-width:780px}.yatoon-table-scroll .num{text-align:right;font-weight:700}.yatoon-table-scroll .negative{color:#b42318}.yatoon-workflow{display:flex;justify-content:space-between;gap:12px;padding:15px 0;border-bottom:1px solid #eaecf0}.yatoon-workflow p{margin:3px 0;color:#475467}.yatoon-empty{text-align:center;padding:40px 20px;color:#667085}.yatoon-empty .dashicons{font-size:30px;width:30px;height:30px}.yatoon-empty strong{display:block;color:#172033;margin-top:8px}.yatoon-health-facts p{display:flex;justify-content:space-between;gap:16px;padding:8px 0;margin:0;border-bottom:1px solid #eaecf0}.yatoon-backup-list{margin-top:16px;border-top:1px solid #eaecf0}.yatoon-backup-list form{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid #eaecf0}.yatoon-empty-state{text-align:center;color:#667085;padding:28px!important}.yatoon-trend-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:16px 0}.yatoon-trend-grid div{padding:14px;border:1px solid #eaecf0;border-radius:8px;background:#f8fafc}.yatoon-trend-grid strong,.yatoon-trend-grid span{display:block}.yatoon-trend-grid strong{font-size:22px}.yatoon-trend-grid span{color:#667085}.yatoon-technical-summary{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;background:#f8fafc}.yatoon-copy-status{display:block;min-height:20px;color:#15803d;margin-top:6px}@media(max-width:782px){.yatoon-ops-head{display:block}.yatoon-ops-head .button{margin-top:14px}.yatoon-ops-kpis,.yatoon-ops-grid{grid-template-columns:1fr}.panel-title{display:block}.panel-title form,.panel-title .button{margin-top:10px}.yatoon-check{grid-template-columns:26px 1fr}.yatoon-status{grid-column:2}.yatoon-ops-panel{padding:16px}.yatoon-health-facts p,.yatoon-backup-list form{align-items:flex-start;flex-direction:column}.yatoon-trend-grid{grid-template-columns:repeat(2,1fr)}}
        </style><?php
    }
}
