<?php

namespace Kanzu\StoreVoiceAI;

// Prevent direct file access.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

use Kanzu\StoreVoiceAI\Services\Store_Voice_AI_API;

class Storevoiceai_Admin_Page
{
    use Working_Hours_Trait;

    /**
     * Wallet top-up amounts (USD) offered to merchants. Shared between the
     * UI options builder and the server-side amount validation so both stay
     * in sync.
     */
    private const TOPUP_AMOUNTS = array(10, 25, 50, 100, 200, 500, 1000);

    /**
     * Transient key + lifetime for the cached Stripe publishable key sourced
     * from the billing backend.
     */
    private const STRIPE_PK_TRANSIENT = '_storevoiceai_stripe_publishable_key';
    private const STRIPE_PK_TTL       = HOUR_IN_SECONDS;

    public function add_admin_menu()
    {
        add_menu_page(
            esc_html__('StoreVoiceAI', 'storevoiceai-for-woocommerce'),
            esc_html__('StoreVoiceAI', 'storevoiceai-for-woocommerce'),
            'manage_woocommerce',
            'storevoiceai-for-woocommerce',
            array($this, 'render_dashboard_page'),
            'dashicons-phone',
            56
        );

        add_submenu_page(
            'storevoiceai-for-woocommerce',
            esc_html__('Dashboard', 'storevoiceai-for-woocommerce'),
            esc_html__('Dashboard', 'storevoiceai-for-woocommerce'),
            'manage_woocommerce',
            'storevoiceai-for-woocommerce-dashboard',
            array($this, 'render_dashboard_page')
        );

        remove_submenu_page('storevoiceai-for-woocommerce', 'storevoiceai-for-woocommerce');

        add_submenu_page(
            'storevoiceai-for-woocommerce',
            esc_html__('Call Logs', 'storevoiceai-for-woocommerce'),
            esc_html__('Call Logs', 'storevoiceai-for-woocommerce'),
            'manage_woocommerce',
            'storevoiceai-for-woocommerce-call-logs',
            array($this, 'render_call_logs_page')
        );

        add_submenu_page(
            'storevoiceai-for-woocommerce',
            esc_html__('Billing & Usage', 'storevoiceai-for-woocommerce'),
            esc_html__('Billing & Usage', 'storevoiceai-for-woocommerce'),
            'manage_woocommerce',
            'storevoiceai-for-woocommerce-billing',
            array($this, 'render_billing_page')
        );

        add_submenu_page(
            'storevoiceai-for-woocommerce',
            esc_html__('Settings', 'storevoiceai-for-woocommerce'),
            esc_html__('Settings', 'storevoiceai-for-woocommerce'),
            'manage_woocommerce',
            'storevoiceai-for-woocommerce-settings',
            array($this, 'render_settings_page')
        );
    }

    public function enqueue_admin_assets($hook)
    {
        if (strpos($hook, 'storevoiceai-for-woocommerce') === false) {
            return;
        }

        wp_enqueue_style(
            'storevoiceai-for-woocommerce-admin',
            plugins_url('../assets/css/admin.css', __FILE__),
            array(),
            '0.0.26'
        );

        wp_enqueue_style(
            'storevoiceai-select2',
            plugins_url('../assets/vendor/select2/select2.min.css', __FILE__),
            array(),
            '4.1.0-rc.0'
        );

        wp_enqueue_script(
            'storevoiceai-select2',
            plugins_url('../assets/vendor/select2/select2.min.js', __FILE__),
            array('jquery'),
            '4.1.0-rc.0',
            true
        );

        wp_enqueue_script(
            'storevoiceai-chartjs',
            plugins_url('../assets/vendor/chartjs/chart.umd.min.js', __FILE__),
            array(),
            '4.5.1',
            true
        );

        wp_enqueue_script(
            'storevoiceai-stripe-js',
            'https://js.stripe.com/v3/',
            array(),
            null,
            true
        );

        wp_enqueue_script(
            'storevoiceai-for-woocommerce-admin',
            plugins_url('../assets/js/admin.js', __FILE__),
            array('jquery', 'storevoiceai-chartjs', 'storevoiceai-stripe-js'),
            '0.0.26',
            true
        );

        wp_enqueue_script(
            'storevoiceai-for-woocommerce-knowledge-base',
            plugins_url('../assets/js/knowledgeBase.js', __FILE__),
            array('storevoiceai-for-woocommerce-admin', 'storevoiceai-select2'),
            '0.0.7',
            true
        );

        wp_enqueue_script(
            'storevoiceai-for-woocommerce-phone-setup',
            plugins_url('../assets/js/phoneSetup.js', __FILE__),
            array('storevoiceai-for-woocommerce-admin'),
            '0.0.10',
            true
        );

        // Pricing page (package selection), not the direct checkout — lets the
        // user pick a plan before paying.
        $pricing_url = admin_url('admin.php?page=storevoiceai-for-woocommerce-pricing');
        $is_subscribed = false;
        $fs = null;
        if (function_exists('\\Kanzu\\StoreVoiceAI\\storevoiceai_fs')) {
            $fs = \Kanzu\StoreVoiceAI\storevoiceai_fs();
            if ($fs) {
                if (method_exists($fs, 'pricing_url')) {
                    $freemius_pricing_url = $fs->pricing_url();
                    if (!empty($freemius_pricing_url)) {
                        $pricing_url = $freemius_pricing_url;
                    }
                }
                if (method_exists($fs, 'is_paying')) {
                    $is_subscribed = (bool) $fs->is_paying();
                }
            }
        }

        wp_localize_script('storevoiceai-for-woocommerce-admin', 'storevoiceaiCallLogger', array(
            'ajax_url'      => admin_url('admin-ajax.php'),
            'nonce'         => wp_create_nonce('storevoiceai-for-woocommerce-nonce'),
            'has_agent'     => !empty(get_option('_storevoiceai_agentid')),
            'pricing_url'   => $pricing_url,
            'is_subscribed' => $is_subscribed,
            'top_up_options' => $this->get_top_up_options(),
            'billing_url'    => admin_url('admin.php?page=storevoiceai-for-woocommerce-billing'),
            'stripe_publishable_key' => $this->get_stripe_publishable_key(),
            'settings_url'  => admin_url('admin.php?page=storevoiceai-for-woocommerce-settings'),
            'i18n' => array(
                'audioNotSupported' => esc_html__('Your browser does not support the audio element.', 'storevoiceai-for-woocommerce')
            )
        ));

        // First-run spotlight tour. Only ships config when we're on the dashboard
        // page and the current user hasn't dismissed the tour yet; the JS treats
        // an empty/disabled config as a signal to do nothing. Two URLs reach the
        // dashboard render (the top-level slug and the explicit submenu slug), so
        // match on $_GET['page'] rather than the hook suffix.
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen detection
        $current_page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : '';
        $is_dashboard_screen = in_array(
            $current_page,
            array('storevoiceai-for-woocommerce', 'storevoiceai-for-woocommerce-dashboard'),
            true
        );
        $tour_enabled = $is_dashboard_screen
            && ! get_user_meta(get_current_user_id(), '_storevoiceai_dashboard_tour_seen', true);

        $assistant_name = get_option('_storevoiceai_assistant_name', '');
        if ($assistant_name === '') {
            $assistant_name = __('your AI Agent', 'storevoiceai-for-woocommerce');
        }

        wp_localize_script('storevoiceai-for-woocommerce-admin', 'storevoiceaiTour', array(
            'enabled' => $tour_enabled,
            'i18n'    => array(
                'welcomeTitle' => sprintf(
                    /* translators: %s: agent name */
                    __('Meet %s', 'storevoiceai-for-woocommerce'),
                    $assistant_name
                ),
                'welcomeBody' => sprintf(
                    /* translators: %s: agent name */
                    __('A quick 30-second tour of where %s lives in your store.', 'storevoiceai-for-woocommerce'),
                    $assistant_name
                ),
                'widgetTitle'   => __('Where shoppers call from', 'storevoiceai-for-woocommerce'),
                'widgetBody'    => __('Customers on your store tap this button to get support from your agent.', 'storevoiceai-for-woocommerce'),
                'callLogsTitle' => __('See every call here', 'storevoiceai-for-woocommerce'),
                'callLogsBody'  => __('Call Logs holds full transcripts and recordings of every customer call.', 'storevoiceai-for-woocommerce'),
                'settingsTitle' => __('Tune your agent in Settings', 'storevoiceai-for-woocommerce'),
                'settingsBody'  => __('Update voice, name, policies, and the phone number from the Settings page.', 'storevoiceai-for-woocommerce'),
                'next'          => __('Next', 'storevoiceai-for-woocommerce'),
                'back'          => __('Back', 'storevoiceai-for-woocommerce'),
                'skip'          => __('Skip tour', 'storevoiceai-for-woocommerce'),
                'done'          => __('Got it', 'storevoiceai-for-woocommerce'),
                'start'         => __('Start tour', 'storevoiceai-for-woocommerce'),
                'stepLabel'     => __('Step %1$d of %2$d', 'storevoiceai-for-woocommerce'),
            ),
        ));
    }

    public function dismiss_dashboard_tour()
    {
        $this->verify_ajax_request();
        update_user_meta(get_current_user_id(), '_storevoiceai_dashboard_tour_seen', 1);
        wp_send_json_success();
    }

    public function render_call_logs_page()
    {
?>
        <div class="wrap">
            <div class="call-logs-header">
                <div class="call-logs-title">
                    <h1><?php esc_html_e('Call Logs', 'storevoiceai-for-woocommerce'); ?></h1>
                    <button type="button" id="storevoiceai-sync-calls" class="storevoiceai-sync-calls" aria-label="<?php esc_attr_e('Sync calls', 'storevoiceai-for-woocommerce'); ?>" title="<?php esc_attr_e('Sync calls', 'storevoiceai-for-woocommerce'); ?>">
                        <span class="dashicons dashicons-update" aria-hidden="true"></span>
                        <span class="spinner" aria-hidden="true"></span>
                    </button>
                </div>
                <div class="call-logs-filters">
                    <label class="call-logs-filter">
                        <span class="call-logs-filter__label"><?php esc_html_e('From', 'storevoiceai-for-woocommerce'); ?></span>
                        <input type="date" id="call-logs-date-from" class="call-logs-date-input" />
                    </label>
                    <label class="call-logs-filter">
                        <span class="call-logs-filter__label"><?php esc_html_e('To', 'storevoiceai-for-woocommerce'); ?></span>
                        <input type="date" id="call-logs-date-to" class="call-logs-date-input" />
                    </label>
                    <button type="button" id="call-logs-date-clear" class="call-logs-date-clear" aria-label="<?php esc_attr_e('Clear date filters', 'storevoiceai-for-woocommerce'); ?>" title="<?php esc_attr_e('Clear date filters', 'storevoiceai-for-woocommerce'); ?>">
                        &times;
                    </button>
                </div>
            </div>
            <div id="call-logs-table" class="call-logs-container">
                <!-- Call logs will be loaded here via AJAX -->
            </div>
            <div id="call-details-modal" class="modal">
                <!-- Call details will be loaded here via AJAX -->
            </div>
        </div>
    <?php
    }

    public function render_dashboard_page()
    {
    ?>
        <div class="wrap">
            <div class="dashboard-header">
                <h1><?php esc_html_e('Call Analytics Dashboard', 'storevoiceai-for-woocommerce'); ?></h1>
                <div class="dashboard-range-tabs" role="tablist" aria-label="<?php esc_attr_e('Time range', 'storevoiceai-for-woocommerce'); ?>">
                    <button type="button" class="dashboard-range-tab" data-range-days="1" role="tab"><?php esc_html_e('Day', 'storevoiceai-for-woocommerce'); ?></button>
                    <button type="button" class="dashboard-range-tab" data-range-days="7" role="tab"><?php esc_html_e('Week', 'storevoiceai-for-woocommerce'); ?></button>
                    <button type="button" class="dashboard-range-tab is-active" data-range-days="30" role="tab" aria-selected="true"><?php esc_html_e('Month', 'storevoiceai-for-woocommerce'); ?></button>
                    <button type="button" class="dashboard-range-tab" data-range-days="365" role="tab"><?php esc_html_e('Year', 'storevoiceai-for-woocommerce'); ?></button>
                </div>
            </div>
            <div id="call-summary-dashboard" class="dashboard-container">
                <!-- Dashboard data will be loaded here via AJAX -->
            </div>
        </div>
    <?php
    }

    public function render_billing_page()
    {
    ?>
        <div class="wrap">
            <div class="billing-page-header">
                <h1><?php esc_html_e('Billing & Usage', 'storevoiceai-for-woocommerce'); ?></h1>
                <p><?php esc_html_e('Track your call minutes, wallet balance, and billing activity.', 'storevoiceai-for-woocommerce'); ?></p>
            </div>

            <div id="billing-and-usage" class="dashboard-container billing-dashboard">
                <div id="storevoiceai-billing-summary" class="billing-summary-panel" hidden>
                    <!-- Billing summary will be loaded here via AJAX -->
                </div>

                <div class="billing-cards-grid">
                    <div id="storevoiceai-minutes-quota" class="billing-card billing-card--plan">
                        <?php $this->render_billing_card_skeleton(); ?>
                    </div>
                    <div id="storevoiceai-wallet-card" class="billing-card billing-card--wallet">
                        <?php $this->render_billing_card_skeleton(); ?>
                    </div>
                    <div id="storevoiceai-rate-card" class="billing-card billing-card--rate">
                        <?php $this->render_billing_card_skeleton(); ?>
                    </div>
                </div>

                <div class="billing-chart-panel">
                    <div class="billing-panel-header">
                        <div>
                            <h3><?php esc_html_e('Daily Usage', 'storevoiceai-for-woocommerce'); ?></h3>
                            <div id="storevoiceai-usage-totals" class="billing-usage-totals" aria-live="polite">
                                <!-- Usage totals will be loaded here via AJAX -->
                            </div>
                        </div>
                        <div class="dashboard-range-tabs" role="tablist" aria-label="<?php esc_attr_e('Time range', 'storevoiceai-for-woocommerce'); ?>">
                            <button type="button" class="dashboard-range-tab minutes-range-tab" data-range-days="1" role="tab"><?php esc_html_e('Day', 'storevoiceai-for-woocommerce'); ?></button>
                            <button type="button" class="dashboard-range-tab minutes-range-tab" data-range-days="7" role="tab"><?php esc_html_e('Week', 'storevoiceai-for-woocommerce'); ?></button>
                            <button type="button" class="dashboard-range-tab minutes-range-tab is-active" data-range-days="30" role="tab" aria-selected="true"><?php esc_html_e('Month', 'storevoiceai-for-woocommerce'); ?></button>
                            <button type="button" class="dashboard-range-tab minutes-range-tab" data-range-days="365" role="tab"><?php esc_html_e('Year', 'storevoiceai-for-woocommerce'); ?></button>
                        </div>
                    </div>
                    <div class="billing-chart-canvas">
                        <canvas id="storevoiceai-minutes-plot"></canvas>
                        <?php $this->render_billing_chart_skeleton(); ?>
                    </div>
                </div>

                <div class="billing-tables-grid">
                    <div class="billing-table-panel">
                        <div class="billing-panel-header">
                            <h3><?php esc_html_e('Usage Breakdown', 'storevoiceai-for-woocommerce'); ?></h3>
                        </div>
                        <div id="storevoiceai-usage-breakdown" class="billing-table-wrap">
                            <?php $this->render_billing_table_skeleton(); ?>
                        </div>
                    </div>

                    <div class="billing-table-panel">
                        <div class="billing-panel-header">
                            <h3><?php esc_html_e('Wallet Transactions', 'storevoiceai-for-woocommerce'); ?></h3>
                        </div>
                        <div id="storevoiceai-wallet-transactions" class="billing-table-wrap">
                            <?php $this->render_billing_table_skeleton(); ?>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    <?php
    }

    /**
     * Server-rendered skeletons so the shimmer is visible on first paint,
     * before any JS runs. The JS skeleton helpers in admin.js emit identical
     * markup, so the handoff to the AJAX-driven render is seamless.
     */
    private function render_billing_card_skeleton()
    {
    ?>
        <span class="minutes-skeleton minutes-skeleton--title">&nbsp;</span>
        <span class="minutes-skeleton minutes-skeleton--usage">&nbsp;</span>
        <span class="minutes-skeleton minutes-skeleton--line">&nbsp;</span>
        <div class="minutes-progress"><div class="minutes-progress__bar minutes-skeleton"></div></div>
    <?php
    }

    private function render_billing_table_skeleton()
    {
    ?>
        <div class="billing-table-skeleton">
            <span class="minutes-skeleton minutes-skeleton--line">&nbsp;</span>
            <span class="minutes-skeleton minutes-skeleton--line">&nbsp;</span>
            <span class="minutes-skeleton minutes-skeleton--line">&nbsp;</span>
            <span class="minutes-skeleton minutes-skeleton--line">&nbsp;</span>
        </div>
    <?php
    }

    private function render_billing_chart_skeleton()
    {
        $heights = array(55, 80, 40, 95, 65, 75, 50, 88, 60, 70, 45, 82);
    ?>
        <div class="billing-chart-skeleton" role="status" aria-label="<?php esc_attr_e('Loading usage chart', 'storevoiceai-for-woocommerce'); ?>">
            <?php foreach ($heights as $height) : ?>
                <span class="billing-chart-skeleton__bar" style="height:<?php echo esc_attr($height); ?>%"></span>
            <?php endforeach; ?>
        </div>
    <?php
    }

    private function get_top_up_options()
    {
        $options = array();

        foreach (self::TOPUP_AMOUNTS as $amount) {
            $options[] = array(
                'amount' => $amount,
                'label'  => '$' . number_format_i18n($amount),
            );
        }

        return $options;
    }

    /**
     * Resolve the Stripe publishable key used by the browser checkout.
     *
     * Resolution order:
     *   1. A STOREVOICEAI_STRIPE_PUBLISHABLE_KEY constant (lets each
     *      environment pin its own key without touching code).
     *   2. The key advertised by the billing backend's connection-status
     *      endpoint, cached in a transient so we don't hit the API on every
     *      admin page load.
     *
     * Returns an empty string when no key is available; the JS guards against
     * an empty key before initialising Stripe.
     */
    private function get_stripe_publishable_key()
    {
        if (defined('STOREVOICEAI_STRIPE_PUBLISHABLE_KEY') && STOREVOICEAI_STRIPE_PUBLISHABLE_KEY !== '') {
            return (string) STOREVOICEAI_STRIPE_PUBLISHABLE_KEY;
        }

        $cached = get_transient(self::STRIPE_PK_TRANSIENT);
        if (is_string($cached) && $cached !== '') {
            return $cached;
        }

        $api      = new Store_Voice_AI_API();
        $response = $api->get_billing_connection_status();
        if (is_wp_error($response) || !is_array($response)) {
            return '';
        }

        // The backend may nest the key under a `data` envelope or expose it
        // at the top level; accept either and the common key spellings.
        $payload = isset($response['data']) && is_array($response['data']) ? $response['data'] : $response;
        $key     = '';
        foreach (array('stripe_publishable_key', 'stripePublishableKey', 'publishable_key', 'publishableKey') as $candidate) {
            if (!empty($payload[$candidate]) && is_string($payload[$candidate])) {
                $key = $payload[$candidate];
                break;
            }
        }

        if ($key !== '') {
            set_transient(self::STRIPE_PK_TRANSIENT, $key, self::STRIPE_PK_TTL);
        }

        return $key;
    }

    public function render_settings_page()
    {
        $assistant_name          = get_option('_storevoiceai_assistant_name', 'Grace');
        $assistant_first_message = get_option(
            '_storevoiceai_assistant_first_message',
            "Hi, welcome to our store's support line! I'm Grace, here to help you with your shopping needs. What can I help you with today?"
        );
        $assistant_first_message = wp_unslash($assistant_first_message);
        $assistant_voice         = get_option('_storevoiceai_assistant_voice', '');
        // Canonical transfer-phone option is shared with the onboarding wizard
        // — keeping a single source of truth lets either screen edit it.
        $assistant_transfer_phone = get_option(\Kanzu\StoreVoiceAI\Onboarding::OPT_ESCALATION_VAL, '');
        $assistant_escalation_mode = get_option(\Kanzu\StoreVoiceAI\Onboarding::OPT_ESCALATION, 'hours');
        $assistant_working_hours   = $this->get_working_hours();
        // Shipping/returns policy the agent reads from. Shared with the
        // onboarding wizard via the same options.
        $shipping_policy = wp_unslash(get_option('_storevoiceai_shipping_policy', ''));
        $returns_policy  = wp_unslash(get_option('_storevoiceai_returns_policy', ''));
        // Contact email for questions the agent can't answer; defaults to the
        // site admin email until overridden.
        $owner_email = get_option('_storevoiceai_owner_email', '');
        if ($owner_email === '') {
            $owner_email = (string) get_option('admin_email', '');
        }

        $agent_id = get_option('_storevoiceai_agentid');
        $has_agent = !empty($agent_id);
        $assistant_button_name = $assistant_name !== '' ? $assistant_name : __('Support Agent', 'storevoiceai-for-woocommerce');
    ?>
        <div class="wrap">
            <h1><?php esc_html_e('Settings', 'storevoiceai-for-woocommerce'); ?></h1>
            <div id="storevoiceai-settings" class="dashboard-container woocommerce-layout__primary">
                <div class="storevoiceai-agent-fields">
                    <div class="storevoiceai-field" id="storevoiceai-settings-left-column">
                        <h4><?php esc_html_e('Voice AI Agent Name', 'storevoiceai-for-woocommerce'); ?></h4>
                        <p class="description">
                            <?php esc_html_e('The name your agent uses to introduce itself to callers.', 'storevoiceai-for-woocommerce'); ?>
                        </p>
                        <input id="storevoiceai-asst-name" name="storevoiceai-asst-name" type="text"
                            placeholder="<?php esc_attr_e('Enter Name...', 'storevoiceai-for-woocommerce'); ?>"
                            value="<?php echo esc_attr($assistant_name); ?>" />

                        <div class="voice-card">
                            <h4><?php esc_html_e('Voice', 'storevoiceai-for-woocommerce'); ?></h4>
                            <div class="voice-filters">
                                <select id="voiceGenderFilter" class="voice-filter">
                                    <option value=""><?php esc_html_e('All genders', 'storevoiceai-for-woocommerce'); ?></option>
                                </select>
                                <select id="voiceAccentFilter" class="voice-filter">
                                    <option value=""><?php esc_html_e('All accents', 'storevoiceai-for-woocommerce'); ?></option>
                                </select>
                            </div>
                            <div class="voice-selector">
                                <button type="button" class="voice-avatar voice-preview__button" id="storevoiceai-voice-preview-button" disabled>
                                    <span class="voice-avatar__initial">&ndash;</span>
                                    <span class="dashicons dashicons-controls-play voice-avatar__play" aria-hidden="true"></span>
                                    <span class="screen-reader-text voice-preview__button-label"><?php esc_html_e('Play voice demo', 'storevoiceai-for-woocommerce'); ?></span>
                                </button>
                                <div class="voice-details">
                                    <div class="dropdown voice-dropdown">
                                        <select id="voiceDropdown" class="voice-native-select" aria-hidden="true" tabindex="-1">
                                            <!-- Populated dynamically via storevoiceai_get_voices AJAX -->
                                        </select>
                                        <button type="button" id="voiceDropdownButton" class="voice-dropdown__button" aria-haspopup="listbox" aria-expanded="false">
                                            <span id="voiceDropdownLabel"><?php esc_html_e('Loading voices...', 'storevoiceai-for-woocommerce'); ?></span>
                                            <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
                                        </button>
                                        <div id="voiceDropdownMenu" class="voice-dropdown__menu" role="listbox" hidden></div>
                                    </div>
                                </div>
                            </div>
                            <audio id="storevoiceai-voice-preview-audio" preload="none">
                                <?php esc_html_e('Your browser does not support the audio element.', 'storevoiceai-for-woocommerce'); ?>
                            </audio>
                        </div>
                    </div>
                    <div class="storevoiceai-field" id="storevoiceai-settings-right-column">
                        <div class="storevoiceai-transfer-card storevoiceai-transfer-card--loading">
                            <div class="storevoiceai-transfer-skeleton" role="status" aria-label="<?php esc_attr_e('Loading transfer settings…', 'storevoiceai-for-woocommerce'); ?>">
                                <div class="storevoiceai-transfer-skeleton__line storevoiceai-transfer-skeleton__line--title"></div>
                                <div class="storevoiceai-transfer-skeleton__line storevoiceai-transfer-skeleton__line--text"></div>
                                <div class="storevoiceai-transfer-skeleton__input"></div>
                                <div class="storevoiceai-transfer-skeleton__divider"></div>
                                <div class="storevoiceai-transfer-skeleton__switch">
                                    <div class="storevoiceai-transfer-skeleton__line storevoiceai-transfer-skeleton__line--text"></div>
                                    <div class="storevoiceai-transfer-skeleton__toggle"></div>
                                </div>
                            </div>
                            <div class="storevoiceai-transfer-phone-section">
                                <h5><?php esc_html_e('Transfer Phone Number', 'storevoiceai-for-woocommerce'); ?></h5>
                                <p class="description">
                                    <?php esc_html_e("Calls the agent can't resolve are transferred to this number. Leave blank to disable transfers.", 'storevoiceai-for-woocommerce'); ?>
                                </p>
                                <input id="storevoiceai-asst-transfer-phone"
                                    name="storevoiceai-asst-transfer-phone"
                                    type="tel"
                                    aria-describedby="storevoiceai-asst-transfer-phone-error"
                                    placeholder="<?php esc_attr_e('+1 555 000 0000', 'storevoiceai-for-woocommerce'); ?>"
                                    value="<?php echo esc_attr($assistant_transfer_phone); ?>" />
                                <p class="storevoiceai-field-error" id="storevoiceai-asst-transfer-phone-error" role="alert" hidden></p>
                            </div>

                            <div class="storevoiceai-working-hours-card">
                                <div class="storevoiceai-switch-row">
                                    <div>
                                        <span class="storevoiceai-switch-row__label"><?php esc_html_e('Only transfer during working hours', 'storevoiceai-for-woocommerce'); ?></span>
                                        <span class="storevoiceai-switch-row__hint"><?php esc_html_e('These hours control when calls transfer; outside them, callers leave a message.', 'storevoiceai-for-woocommerce'); ?></span>
                                    </div>
                                    <button type="button"
                                        id="storevoiceai-working-hours-toggle"
                                        class="storevoiceai-switch<?php echo $assistant_escalation_mode !== 'always' ? ' storevoiceai-switch--on' : ''; ?>"
                                        role="switch"
                                        aria-checked="<?php echo $assistant_escalation_mode !== 'always' ? 'true' : 'false'; ?>">
                                        <span class="storevoiceai-switch__thumb"></span>
                                    </button>
                                </div>
                                <div id="storevoiceai-working-hours-panel" class="storevoiceai-hours" <?php echo $assistant_escalation_mode === 'always' ? 'hidden' : ''; ?>>
                                    <?php foreach (self::week_day_keys() as $day_key) :
                                        $day = $assistant_working_hours[$day_key];
                                        $day_label = $this->working_hours_day_label($day_key);
                                    ?>
                                        <div class="storevoiceai-hours__row" data-day="<?php echo esc_attr($day_key); ?>">
                                            <span class="storevoiceai-hours__day"><?php echo esc_html($day_label); ?></span>
                                            <label class="storevoiceai-hours__off">
                                                <input type="checkbox"
                                                    class="storevoiceai-hours__off-box"
                                                    <?php checked(!empty($day['off'])); ?>
                                                    aria-label="<?php echo esc_attr(sprintf(__('%s day off', 'storevoiceai-for-woocommerce'), $day_label)); ?>" />
                                                <span><?php esc_html_e('Day off', 'storevoiceai-for-woocommerce'); ?></span>
                                            </label>
                                            <div class="storevoiceai-hours__times" <?php echo !empty($day['off']) ? 'style="visibility:hidden;"' : ''; ?>>
                                                <input type="time"
                                                    class="storevoiceai-hours__time storevoiceai-hours__start"
                                                    value="<?php echo esc_attr($day['start']); ?>"
                                                    aria-label="<?php echo esc_attr(sprintf(__('%s start time', 'storevoiceai-for-woocommerce'), $day_label)); ?>" />
                                                <span class="storevoiceai-hours__dash">&ndash;</span>
                                                <input type="time"
                                                    class="storevoiceai-hours__time storevoiceai-hours__end"
                                                    value="<?php echo esc_attr($day['end']); ?>"
                                                    aria-label="<?php echo esc_attr(sprintf(__('%s end time', 'storevoiceai-for-woocommerce'), $day_label)); ?>" />
                                            </div>
                                        </div>
                                    <?php endforeach; ?>
                                </div>
                            </div>
                        </div>

                        <div class="voice-card" id="storevoiceai-greeting-card">
                            <h4><?php esc_html_e('Voice AI First Message', 'storevoiceai-for-woocommerce'); ?></h4>
                            <p class="description">
                                <?php esc_html_e('The first thing your agent says when it answers a call.', 'storevoiceai-for-woocommerce'); ?>
                            </p>
                            <textarea class="storevoiceai-greeting"
                                maxlength="1000"
                                id="storevoiceai-asst-greeting"
                                name="storevoiceai-asst-greeting"
                                rows="2"><?php echo esc_textarea($assistant_first_message); ?></textarea>
                        </div>
                        <div class="voice-card" id="storevoiceai-owner-email-card">
                            <h4><?php esc_html_e('Contact email for unanswered questions', 'storevoiceai-for-woocommerce'); ?></h4>
                            <input type="email"
                                id="storevoiceai-owner-email"
                                name="storevoiceai-owner-email"
                                value="<?php echo esc_attr($owner_email); ?>"
                                placeholder="<?php esc_attr_e('you@store.com', 'storevoiceai-for-woocommerce'); ?>" />
                            <p class="description">
                                <?php echo esc_html(sprintf(
                                    /* translators: %s: agent name */
                                    __('If %s can’t answer a question, they’ll direct the customer here.', 'storevoiceai-for-woocommerce'),
                                    $assistant_button_name
                                )); ?>
                            </p>
                        </div>
                    </div>
                </div>
                <div class="storevoiceai-policies">
                    <div class="storevoiceai-policy-grid">
                        <div class="storevoiceai-field">
                            <h4><?php esc_html_e('Shipping policy', 'storevoiceai-for-woocommerce'); ?></h4>
                            <textarea class="storevoiceai-greeting"
                                id="storevoiceai-shipping-policy"
                                name="storevoiceai-shipping-policy"
                                rows="3"
                                maxlength="2000"
                                placeholder="<?php esc_attr_e('Describe where you ship and any delivery details…', 'storevoiceai-for-woocommerce'); ?>"><?php echo esc_textarea($shipping_policy); ?></textarea>
                        </div>
                        <div class="storevoiceai-field">
                            <h4><?php esc_html_e('Returns policy', 'storevoiceai-for-woocommerce'); ?></h4>
                            <textarea class="storevoiceai-greeting"
                                id="storevoiceai-returns-policy"
                                name="storevoiceai-returns-policy"
                                rows="3"
                                maxlength="2000"
                                placeholder="<?php esc_attr_e('Describe how returns and refunds work…', 'storevoiceai-for-woocommerce'); ?>"><?php echo esc_textarea($returns_policy); ?></textarea>
                        </div>
                    </div>
                </div>
                <div>
                    <p>
                        <button id="storevoiceai-create-assistant__button"
                            class="button button-primary components-button is-primary"
                            data-has-agent="<?php echo $has_agent ? '1' : '0'; ?>"
                            data-saved-voice="<?php echo esc_attr($assistant_voice); ?>">
                            <?php echo $has_agent
                                ? esc_html(sprintf(
                                    /* translators: %s: agent name */
                                    __('Update %s', 'storevoiceai-for-woocommerce'),
                                    $assistant_button_name
                                ))
                                : esc_html__('Create Agent', 'storevoiceai-for-woocommerce'); ?>
                        </button>
                    </p>
                    <div class="storevoiceai-asst-response"></div>
                </div>
                <div class="phone-setup">
                    <h2 class="section-header"><?php esc_html_e('Phone Setup', 'storevoiceai-for-woocommerce'); ?></h2>
                    <div class="phone-setup-grid">
                        <div class="selector-group">
                            <label for="storevoiceai-country-select"><?php esc_html_e('Select a country', 'storevoiceai-for-woocommerce'); ?></label>
                            <select id="storevoiceai-country-select" class="country-select">
                                <option value="US">&#127482;&#127480; (+1) United States - US</option>
                            </select>
                        </div>

                        <div class="selector-group">
                            <label for="storevoiceai-area-code"><?php esc_html_e('Area code', 'storevoiceai-for-woocommerce'); ?></label>
                            <div class="area-code-wrapper">
                                <input type="text" id="storevoiceai-area-code" placeholder="<?php esc_attr_e('e.g. 415', 'storevoiceai-for-woocommerce'); ?>" />
                                <button id="storevoiceai-search-numbers" class="search-button">
                                    <?php esc_html_e('Get Phone Number', 'storevoiceai-for-woocommerce'); ?>
                                </button>
                            </div>
                        </div>
                    </div>
                    <div id="storevoiceai-phone-numbers-list" aria-live="polite"></div>
                </div>
            </div>

            <!-- Knowledge Base Section -->
            <div class="knowledge-base-section">
                <h2 class="section-header"><?php esc_html_e('Knowledge Base Pages', 'storevoiceai-for-woocommerce'); ?></h2>
                <p class="description">
                    <?php esc_html_e("Pages selected here will be crawled and added to your AI's knowledge base.", 'storevoiceai-for-woocommerce'); ?>
                </p>

                <div class="kb-page-picker">
                    <label for="storevoiceai-kb-page-select">
                        <?php esc_html_e('Select pages', 'storevoiceai-for-woocommerce'); ?>
                    </label>

                    <select id="storevoiceai-kb-page-select"
                        multiple="multiple"
                        class="storevoiceai-kb-page-select">
                        <?php
                        // Render already-saved pages as pre-selected options
                        $saved_pages = get_option('_storevoiceai_knowledge_base_pages', []);
                        foreach ($saved_pages as $page) : ?>
                            <option value="<?php echo esc_attr($page['id']); ?>" selected="selected">
                                <?php echo esc_html($page['title']); ?>
                            </option>
                        <?php endforeach; ?>
                    </select>

                    <div id="storevoiceai-kb-response" class="storevoiceai-kb-response"></div>
                </div>
            </div>
            <p>
                <?php
                printf(
                    /* translators: %s: feedback link */
                    esc_html__('Click this %s to submit your feedback', 'storevoiceai-for-woocommerce'),
                    '<a href="' . esc_url('https://storevoiceai.com') . '">' . esc_html__('link', 'storevoiceai-for-woocommerce') . '</a>'
                );
                ?>
            </p>
        </div>
<?php
    }

    /**
     * Verify the AJAX nonce and that the current user can manage WooCommerce.
     * Sends a JSON error and halts execution if either check fails.
     */
    private function verify_ajax_request()
    {
        check_ajax_referer('storevoiceai-for-woocommerce-nonce', 'nonce');

        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error(
                array('message' => __('You do not have permission to perform this action.', 'storevoiceai-for-woocommerce')),
                403
            );
        }
    }

    private function is_subscribed()
    {
        if (!function_exists('\\Kanzu\\StoreVoiceAI\\storevoiceai_fs')) {
            return false;
        }

        $fs = \Kanzu\StoreVoiceAI\storevoiceai_fs();
        if (!$fs || !method_exists($fs, 'is_paying')) {
            return false;
        }

        return (bool) $fs->is_paying();
    }

    private function extract_phone_number($number)
    {
        if (is_string($number)) {
            return trim($number);
        }

        if (!is_array($number)) {
            return '';
        }

        $phone = $number['number'] ?? $number['phoneNumber'] ?? $number['id'] ?? '';

        return is_string($phone) ? trim($phone) : '';
    }

    private function has_phone_number($numbers)
    {
        if (!is_array($numbers)) {
            return false;
        }

        foreach ($numbers as $number) {
            if ($this->extract_phone_number($number) !== '') {
                return true;
            }
        }

        return false;
    }

    private function has_provisioned_phone_number()
    {
        $api      = new Store_Voice_AI_API();
        $response = $api->get_phone_numbers();

        if (is_wp_error($response)) {
            return false;
        }

        $numbers = $response['data'] ?? $response['numbers'] ?? [];

        return $this->has_phone_number($numbers);
    }

    public function get_call_logs()
    {
        $this->verify_ajax_request();

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_call_logs();

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
        }
        wp_send_json_success($api_response);
    }

    public function sync_calls()
    {
        $this->verify_ajax_request();

        $api = new Store_Voice_AI_API();
        $api_response = $api->sync_calls();

        if (is_wp_error($api_response)) {
            wp_send_json_error(array('message' => $api_response->get_error_message()));
        }

        wp_send_json_success($api_response);
    }

    public function create_voice_assistant()
    {
        $this->verify_ajax_request();

        $assistant_name           = isset($_POST['assistant_name']) ? sanitize_text_field(wp_unslash($_POST['assistant_name'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $assistant_first_message  = isset($_POST['assistant_first_message']) ? sanitize_textarea_field(wp_unslash($_POST['assistant_first_message'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $assistant_voice          = isset($_POST['assistant_voice']) ? sanitize_text_field(wp_unslash($_POST['assistant_voice'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        // Transfer phone is intentionally read with isset (not !empty) so the
        // user can clear it by submitting an empty value.
        $has_transfer_phone       = isset($_POST['assistant_transfer_phone']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $assistant_transfer_phone = $has_transfer_phone ? sanitize_text_field(wp_unslash($_POST['assistant_transfer_phone'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $escalation_mode          = isset($_POST['escalation_mode']) ? sanitize_key(wp_unslash($_POST['escalation_mode'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        // Policies use isset (not !empty) so a field can be cleared.
        $has_shipping_policy      = isset($_POST['shipping_policy']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $shipping_policy          = $has_shipping_policy ? sanitize_textarea_field(wp_unslash($_POST['shipping_policy'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $has_returns_policy       = isset($_POST['returns_policy']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $returns_policy           = $has_returns_policy ? sanitize_textarea_field(wp_unslash($_POST['returns_policy'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $has_owner_email          = isset($_POST['owner_email']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $owner_email              = '';
        if ($has_owner_email) {
            $raw_owner_email = wp_unslash($_POST['owner_email']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
            if (is_array($raw_owner_email)) {
                wp_send_json_error(
                    array('message' => __('Please enter a valid contact email address.', 'storevoiceai-for-woocommerce')),
                    400
                );
            }
            $raw_owner_email = trim((string) $raw_owner_email);
            if ($raw_owner_email !== '' && !is_email($raw_owner_email)) {
                wp_send_json_error(
                    array('message' => __('Please enter a valid contact email address.', 'storevoiceai-for-woocommerce')),
                    400
                );
            }
            $owner_email = sanitize_email($raw_owner_email);
        }
        $working_hours            = null;
        if (isset($_POST['working_hours'])) {  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
            $raw_working_hours = wp_unslash($_POST['working_hours']);  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
            $decoded_hours     = is_string($raw_working_hours) ? json_decode($raw_working_hours, true) : (is_array($raw_working_hours) ? $raw_working_hours : null);
            if (is_array($decoded_hours)) {
                $working_hours = $this->sanitize_working_hours($decoded_hours);
            }
        }

        if (
            !$this->is_subscribed() ||
            (($has_transfer_phone || $escalation_mode !== '' || $working_hours !== null) && !$this->has_provisioned_phone_number())
        ) {
            $has_transfer_phone = false;
            $escalation_mode    = '';
            $working_hours      = null;
        }

        $agent_id = get_option('_storevoiceai_agentid');

        if (empty($agent_id)) {
            $api      = new Store_Voice_AI_API();
            $response = $api->create_agent($assistant_name ?: 'Grace');

            if (is_wp_error($response)) {
                wp_send_json_error($response->get_error_message());
                return;
            }

            $agent_id = $response['agent_id'];
            update_option('_storevoiceai_agentid', $agent_id, false);
        }

        $body = [];
        if (!empty($assistant_name)) {
            $body['personaName'] = $assistant_name;
        }
        if (!empty($assistant_first_message)) {
            $body['firstMessage'] = $assistant_first_message;
        }
        if (!empty($assistant_voice)) {
            $body['voiceId'] = $assistant_voice;
        }
        if ($has_transfer_phone) {
            $body['transferPhoneNumber'] = $assistant_transfer_phone; // empty string clears it upstream
        }

        // Policies and contact email are site-level settings now, pushed to
        // the dedicated site endpoint rather than the agent.
        $site_body = [];
        if ($has_shipping_policy) {
            $site_body['shippingPolicy'] = $shipping_policy; // empty string clears it upstream
        }
        if ($has_returns_policy) {
            $site_body['returnsPolicy'] = $returns_policy; // empty string clears it upstream
        }
        if ($has_owner_email) {
            $site_body['ownerEmail'] = $owner_email; // empty string clears it upstream
        }

        if (!empty($body) || !empty($site_body)) {
            $api = new Store_Voice_AI_API();

            if (!empty($body)) {
                $api_response = $api->update_agent($agent_id, $body);

                if (is_wp_error($api_response)) {
                    wp_send_json_error($api_response->get_error_message());
                    return;
                }
            }

            if (!empty($site_body)) {
                $site_response = $api->update_site($site_body);

                if (is_wp_error($site_response)) {
                    wp_send_json_error($site_response->get_error_message());
                    return;
                }
            }
        }

        if (!empty($assistant_name)) {
            update_option('_storevoiceai_assistant_name', $assistant_name);
        }
        if (!empty($assistant_voice)) {
            update_option('_storevoiceai_assistant_voice', $assistant_voice);
        }
        if (!empty($assistant_first_message)) {
            update_option('_storevoiceai_assistant_first_message', $assistant_first_message);
        }
        if ($has_transfer_phone) {
            // Mirror the onboarding wizard's canonical option so both screens
            // read from the same source.
            update_option(\Kanzu\StoreVoiceAI\Onboarding::OPT_ESCALATION_VAL, $assistant_transfer_phone);
        }
        if ($has_shipping_policy) {
            update_option('_storevoiceai_shipping_policy', $shipping_policy);
        }
        if ($has_returns_policy) {
            update_option('_storevoiceai_returns_policy', $returns_policy);
        }
        if ($has_owner_email) {
            update_option('_storevoiceai_owner_email', $owner_email);
        }
        if (in_array($escalation_mode, array('always', 'hours', 'email'), true)) {
            update_option(\Kanzu\StoreVoiceAI\Onboarding::OPT_ESCALATION, $escalation_mode);
        }
        if (is_array($working_hours)) {
            update_option(\Kanzu\StoreVoiceAI\Onboarding::OPT_WORKING_HOURS, $working_hours);
            $this->sync_default_availability_profile($working_hours);
        }

        wp_send_json_success([
            'agent_id' => $agent_id,
            'message'  => __('Agent updated successfully.', 'storevoiceai-for-woocommerce'),
        ]);
    }

    public function get_voice_usage()
    {
        $this->verify_ajax_request();

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_voice_usage();

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
        }

        wp_send_json_success($api_response);
    }

    public function get_voice_usage_daily()
    {
        $this->verify_ajax_request();

        $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_voice_usage_daily($days);

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
        }

        wp_send_json_success($api_response);
    }

    public function get_billing_connection_status()
    {
        $this->verify_ajax_request();

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_billing_connection_status();

        if (is_wp_error($api_response)) {
            $error_data = $api_response->get_error_data();
            $status = is_array($error_data) && isset($error_data['status']) ? (int) $error_data['status'] : 500;

            wp_send_json_error(
                array('message' => $api_response->get_error_message()),
                $status
            );
        }

        wp_send_json_success($api_response);
    }

    public function get_wallet_transactions()
    {
        $this->verify_ajax_request();

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_wallet_transactions();

        if (is_wp_error($api_response)) {
            $error_data = $api_response->get_error_data();
            $status = is_array($error_data) && isset($error_data['status']) ? (int) $error_data['status'] : 500;

            wp_send_json_error(
                array('message' => $api_response->get_error_message()),
                $status
            );
        }

        wp_send_json_success(array(
            'transactions' => $this->normalize_wallet_transactions($api_response),
        ));
    }

    /**
     * Shape the raw PaymentTransaction array from /transactions/payments into the
     * fields the billing table consumes. Backend amounts and balances are integer
     * cents; convert to USD here so the JS layer only formats, never converts.
     */
    private function normalize_wallet_transactions($api_response)
    {
        if (!is_array($api_response)) {
            return array();
        }

        $type_labels = array(
            'topupCredit'   => __('Top up', 'storevoiceai-for-woocommerce'),
            'subscription'  => __('Subscription', 'storevoiceai-for-woocommerce'),
            'onboardingFee' => __('Onboarding fee', 'storevoiceai-for-woocommerce'),
        );

        $normalized = array();
        foreach ($api_response as $transaction) {
            if (!is_array($transaction)) {
                continue;
            }

            $type = isset($transaction['transactionType']) ? (string) $transaction['transactionType'] : '';
            $cents = isset($transaction['amount']) ? (int) $transaction['amount'] : 0;
            // Top-ups credit the wallet (positive); everything else is a debit.
            $amount = ($type === 'topupCredit' ? $cents : -$cents) / 100;

            $normalized[] = array(
                'date'    => isset($transaction['transactionDate']) ? (string) $transaction['transactionDate'] : '',
                'type'    => isset($type_labels[$type]) ? $type_labels[$type] : ($type !== '' ? $type : __('Wallet transaction', 'storevoiceai-for-woocommerce')),
                'amount'  => $amount,
                'balance' => isset($transaction['balanceAfter']) ? ((int) $transaction['balanceAfter']) / 100 : null,
            );
        }

        return $normalized;
    }

    public function create_stripe_top_up_session()
    {
        $this->verify_ajax_request();

        $allowed_amounts = self::TOPUP_AMOUNTS;
        $raw_amount = isset($_POST['amount']) && is_scalar($_POST['amount']) ? wp_unslash($_POST['amount']) : 0;  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $amount = absint($raw_amount);

        if (!in_array($amount, $allowed_amounts, true)) {
            wp_send_json_error(
                array('message' => __('Please choose a valid top-up amount.', 'storevoiceai-for-woocommerce')),
                400
            );
        }

        $default_return_url = add_query_arg(
            array(
                'page'               => 'storevoiceai-for-woocommerce-billing',
                'storevoiceai_topup' => 'processing',
                'topup_amount'       => $amount,
            ),
            admin_url('admin.php')
        );
        $raw_return_url = isset($_POST['return_url']) && is_scalar($_POST['return_url']) ? wp_unslash($_POST['return_url']) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request().
        $posted_return_url = esc_url_raw($raw_return_url);
        $return_url = $posted_return_url !== ''
            ? wp_validate_redirect($posted_return_url, $default_return_url)
            : $default_return_url;

        $api = new Store_Voice_AI_API();
        $api_response = $api->create_stripe_top_up_session($amount, $return_url);

        if (is_wp_error($api_response)) {
            $error_data = $api_response->get_error_data();
            $status = is_array($error_data) && isset($error_data['status']) ? (int) $error_data['status'] : 500;

            wp_send_json_error(
                array('message' => $api_response->get_error_message()),
                $status
            );
        }

        $client_secret = '';
        $checkout_url = '';
        if (is_array($api_response)) {
            // The Stripe client secret is an opaque token from a trusted backend response.
            // Do not run it through sanitize_text_field(): that strips/collapses characters
            // and corrupts the token, which makes Stripe reject it with "Invalid Checkout
            // session" / "Timed out waiting for client secret". Only trim surrounding space.
            $client_secret = isset($api_response['clientSecret'])
                ? trim((string) $api_response['clientSecret'])
                : trim((string) ($api_response['client_secret'] ?? ''));
            $checkout_url = isset($api_response['checkoutUrl'])
                ? esc_url_raw((string) $api_response['checkoutUrl'])
                : esc_url_raw((string) ($api_response['checkout_url'] ?? ($api_response['url'] ?? '')));
        }

        if ($client_secret === '' && $checkout_url === '') {
            wp_send_json_error(
                array('message' => __('Stripe checkout could not be started. Please try again.', 'storevoiceai-for-woocommerce')),
                502
            );
        }

        wp_send_json_success(array(
            'clientSecret' => $client_secret,
            'checkoutUrl'  => $checkout_url,
        ));
    }

    public function get_call_details()
    {
        $this->verify_ajax_request();

        if (!isset($_POST['call_id'])) {  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
            wp_send_json_error('Call ID is required');
        }

        $call_id = sanitize_text_field(wp_unslash($_POST['call_id']));  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_call_details($call_id);

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
        }

        wp_send_json_success($api_response);
    }

    public function get_call_summary()
    {
        $this->verify_ajax_request();

        $days = isset($_POST['days']) ? (int) $_POST['days'] : 30;  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        $api = new Store_Voice_AI_API();
        $api_response = $api->get_call_summary($days);

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
        }

        wp_send_json_success($api_response);
    }

    public function handle_get_phone_numbers()
    {
        $this->verify_ajax_request();

        $api      = new Store_Voice_AI_API();
        $response = $api->get_phone_numbers();

        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => $response->get_error_message()));
        }

        $numbers = $response['data'] ?? $response['numbers'] ?? [];
        wp_send_json_success(array('numbers' => $numbers));
    }

    public function handle_purchase_phone_number()
    {
        $this->verify_ajax_request();

        if (!$this->is_subscribed()) {
            wp_send_json_error(
                array('message' => __('Phone numbers are only available for subscribed users. Choose a plan to continue.', 'storevoiceai-for-woocommerce')),
                403
            );
            return;
        }

        $has_area_code = isset($_POST['area_code']) && $_POST['area_code'] !== '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $area_code     = $has_area_code ? absint(wp_unslash($_POST['area_code'])) : 0;  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        if ($has_area_code && ($area_code < 200 || $area_code > 999)) {
            wp_send_json_error(array('message' => __('Please enter a valid area code.', 'storevoiceai-for-woocommerce')));
            return;
        }

        $api      = new Store_Voice_AI_API();
        $existing = $api->get_phone_numbers();

        if (is_wp_error($existing)) {
            wp_send_json_error(array('message' => $existing->get_error_message()));
            return;
        }

        $existing_numbers = $existing['data'] ?? $existing['numbers'] ?? [];
        if ($this->has_phone_number($existing_numbers)) {
            wp_send_json_error(
                array('message' => __('Your store already has a phone number. Delete it before getting another one.', 'storevoiceai-for-woocommerce')),
                409
            );
            return;
        }

        $response = $api->purchase_phone_number($has_area_code ? $area_code : null);

        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => $response->get_error_message()));
        }

        $number = is_array($response) && isset($response['data']) ? $response['data'] : $response;

        $phone = $this->extract_phone_number($number);

        if (empty($phone)) {
            $message = is_array($response) && !empty($response['message'])
                ? $response['message']
                : __('No phone number is available in that area code. Try another area code.', 'storevoiceai-for-woocommerce');
            wp_send_json_error(array('message' => $message));
            return;
        }

        wp_send_json_success(array('number' => $number));
    }

    public function handle_delete_phone_number()
    {
        $this->verify_ajax_request();

        $phone_number = isset($_POST['phone_number']) ? sanitize_text_field(wp_unslash($_POST['phone_number'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        if (empty($phone_number)) {
            wp_send_json_error(array('message' => __('Phone number is required.', 'storevoiceai-for-woocommerce')));
            return;
        }

        $api      = new Store_Voice_AI_API();
        $response = $api->delete_phone_number($phone_number);

        if (is_wp_error($response)) {
            wp_send_json_error(array('message' => $response->get_error_message()));
        }

        wp_send_json_success(array(
            'phone_number' => $phone_number,
            'message'      => __('Phone number deleted successfully.', 'storevoiceai-for-woocommerce'),
        ));
    }

    /**
     * Finds About, Services and Contact pages by their known slugs.
     * Called once on settings page load when no pages are saved yet.
     */
    public function get_knowledge_base_pages()
    {
        $this->verify_ajax_request();

        $target_slugs = [
            'about',
            'about-us',
            'our-story',
            'services',
            'our-services',
            'contact',
            'contact-us',
            'shop'
        ];

        $found = [];
        $seen  = [];

        foreach ($target_slugs as $slug) {
            $page = get_page_by_path($slug);
            if (!$page || $page->post_status !== 'publish') {
                continue;
            }

            if (in_array($page->ID, $seen)) {
                continue;
            }

            $found[] = [
                'id'    => $page->ID,
                'title' => get_the_title($page->ID),
            ];
            $seen[] = $page->ID;
        }

        wp_send_json_success(['pages' => $found]);
    }

    /**
     * Saves the full Knowledge Base page selection.
     * Diffs against currently saved pages — syncs additions and removals with the API.
     */
    public function create_knowledge_base_pages()
    {
        $this->verify_ajax_request();

        $raw_ids  = isset($_POST['page_ids']) ? (array) wp_unslash($_POST['page_ids']) : [];  // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.
        $page_ids = array_values(array_filter(array_map('absint', $raw_ids)));

        $current_pages = get_option('_storevoiceai_knowledge_base_pages', []);
        $current_ids   = array_map('intval', array_column($current_pages, 'id'));

        $added_ids = array_diff($page_ids, $current_ids);

        $api = new Store_Voice_AI_API();

        $updated_pages = [];

        foreach ($current_pages as $page) {
            if (in_array((int) $page['id'], $page_ids)) {
                $updated_pages[] = $page;
            }
        }

        foreach ($added_ids as $added_id) {
            $page = get_post($added_id);
            if (!$page || $page->post_type !== 'page' || $page->post_status !== 'publish') {
                continue;
            }

            $updated_pages[] = [
                'id'    => $added_id,
                'title' => get_the_title($added_id),
                'url'   => get_permalink($added_id),
            ];
        }

        $api_response = $api->create_knowledge_base_pages($updated_pages);

        if (is_wp_error($api_response)) {
            wp_send_json_error([
                'message' => __('Failed to update knowledge base. Please try again.', 'storevoiceai-for-woocommerce'),
                'error'   => $api_response->get_error_message(),
            ]);
            return;
        }

        update_option('_storevoiceai_knowledge_base_pages', $updated_pages);

        wp_send_json_success([
            'message' => __('Knowledge Base updated successfully.', 'storevoiceai-for-woocommerce'),
            'pages'   => $updated_pages,
        ]);
    }

    public function get_voices()
    {
        $this->verify_ajax_request();

        $api          = new Store_Voice_AI_API();
        $api_response = $api->get_voices();

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
            return;
        }

        wp_send_json_success($api_response);
    }

    public function update_agent_voice()
    {
        $this->verify_ajax_request();

        $voice_id = isset($_POST['voice_id']) ? sanitize_text_field(wp_unslash($_POST['voice_id'])) : '';  // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce + capability verified in verify_ajax_request(); array values are sanitized below.

        if (empty($voice_id)) {
            wp_send_json_error('Voice ID is required.');
            return;
        }

        $agent_id = get_option('_storevoiceai_agentid');

        if (empty($agent_id)) {
            wp_send_json_error('No agent found. Please create an agent first.');
            return;
        }

        $api          = new Store_Voice_AI_API();
        $api_response = $api->update_agent($agent_id, ['voiceId' => $voice_id]);

        if (is_wp_error($api_response)) {
            wp_send_json_error($api_response->get_error_message());
            return;
        }

        update_option('_storevoiceai_assistant_voice', $voice_id);

        wp_send_json_success($api_response);
    }
}
