<?php

namespace BookWellAB;

defined('ABSPATH') || exit;

if (!class_exists('BookWellAB_Customer_Panel')) {

    class BookWellAB_Customer_Panel
    {
        private static $instance = null;
        private static $shortcode_rendered = false;
        private $bwdb;

        public static function get_instance()
        {
            if (self::$instance === null) {
                self::$instance = new self();
            }
            return self::$instance;
        }

        public function __construct()
        {
            $this->bwdb = new BookWellAB_Database();

            add_shortcode('bookwellnow_customer_panel', [$this, 'render_shortcode']);
            add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);

            add_action('wp_ajax_bookwellab_cp_get_appointments', [$this, 'ajax_get_appointments']);
            add_action('wp_ajax_bookwellab_cp_cancel_appointment', [$this, 'ajax_cancel_appointment']);
            add_action('wp_ajax_bookwellab_cp_update_profile', [$this, 'ajax_update_profile']);
            add_action('wp_ajax_bookwellab_cp_change_password', [$this, 'ajax_change_password']);
            add_action('wp_ajax_nopriv_bookwellab_cp_login', [$this, 'ajax_login']);
            add_action('wp_ajax_bookwellab_cp_login', [$this, 'ajax_login']);
            add_action('wp_ajax_nopriv_bookwellab_cp_lost_password', [$this, 'ajax_lost_password']);
            add_action('wp_ajax_bookwellab_cp_lost_password', [$this, 'ajax_lost_password']);
        }

        /**
         * @param array<string, mixed> $atts Shortcode attributes.
         * @return string
         */
        public function render_shortcode($atts)
        {
            self::$shortcode_rendered = true;

            if (!wp_script_is('bookwellab-customer-panel', 'enqueued')) {
                $this->enqueue_assets(true);
            }

            ob_start();
            require BOOKWELLAB_HTML_PATH . 'customer_panel_view.php';
            return ob_get_clean();
        }

        /**
         * @param bool $force Force enqueue.
         * @return void
         */
        public function enqueue_assets($force = false)
        {
            if (!$force && !$this->should_load_assets()) {
                return;
            }

            $settings = BookWellAB_Settings::get_settings();
            $appearance = isset($settings['appearance']) && is_array($settings['appearance']) ? $settings['appearance'] : [];
            $font_family = isset($appearance['font_family']) ? sanitize_text_field((string) $appearance['font_family']) : '';
            $style_deps = ['bookwellab-elements-style'];

            if ($font_family !== '') {
                $allowed_fonts = BookWellAB_Settings::get_google_fonts();
                if (in_array($font_family, $allowed_fonts, true)) {
                    $font_family_query = str_replace(' ', '+', $font_family);
                    $google_font_url = 'https://fonts.googleapis.com/css2?family=' . $font_family_query . ':wght@300;400;500;600;700&display=swap';
                    $font_handle = 'bookwellab-cp-google-font-' . sanitize_title($font_family);
                    wp_enqueue_style($font_handle, esc_url($google_font_url), [], BOOKWELLAB_VERSION);
                    $style_deps[] = $font_handle;
                }
            }

            $css_path = BOOKWELLAB_PATH . 'assets/css/customer-panel.css';
            $css_version = file_exists($css_path) ? (string) filemtime($css_path) : BOOKWELLAB_VERSION;
            $js_path = BOOKWELLAB_PATH . 'assets/js/customer_panel.js';
            $js_version = file_exists($js_path) ? (string) filemtime($js_path) : BOOKWELLAB_VERSION;

            wp_enqueue_style(
                'bookwellab-customer-panel',
                BOOKWELLAB_URL . 'assets/css/customer-panel.css',
                $style_deps,
                $css_version
            );

            wp_enqueue_style(
                'bookwellab-elements-style',
                BOOKWELLAB_URL . 'assets/css/elements.css',
                [],
                BOOKWELLAB_VERSION
            );

            wp_enqueue_script('bookwellab-vue-script', BOOKWELLAB_URL . 'assets/js/vue.cjs.min.js', [], BOOKWELLAB_VERSION, true);
            wp_enqueue_script('bookwellab-elements-plus-script', BOOKWELLAB_URL . 'assets/js/elements-plus.js', ['bookwellab-vue-script'], BOOKWELLAB_VERSION, true);
            wp_enqueue_script('bookwellab-axios-script', BOOKWELLAB_URL . 'assets/js/axios.min.js', [], BOOKWELLAB_VERSION, true);
            wp_enqueue_script('bookwellab-qs-script', BOOKWELLAB_URL . 'assets/js/qs.min.js', [], BOOKWELLAB_VERSION, true);

            wp_enqueue_script(
                'bookwellab-customer-panel',
                BOOKWELLAB_URL . 'assets/js/customer_panel.js',
                ['bookwellab-vue-script', 'bookwellab-elements-plus-script', 'bookwellab-axios-script', 'bookwellab-qs-script'],
                $js_version,
                true
            );

            $general = isset($settings['general']) && is_array($settings['general']) ? $settings['general'] : [];
            $currency_code = isset($general['currency_code']) ? (string) $general['currency_code'] : 'USD';
            $currencies = BookWellAB_Settings::get_currencies();
            $general['currency_symbol'] = isset($currencies[$currency_code]['symbol']) ? $currencies[$currency_code]['symbol'] : '$';

            $date_format_setting = isset($general['date_format']) ? (string) $general['date_format'] : 'Y-m-d';
            $time_format_setting = isset($general['time_format']) ? (string) $general['time_format'] : 'H:i';
            $date_format = ($date_format_setting !== 'inherit') ? $date_format_setting : get_option('date_format');
            $time_format = ($time_format_setting !== 'inherit') ? $time_format_setting : get_option('time_format');

            $customer = BookWellAB_Customer_Account::get_current_panel_customer();
            $user = wp_get_current_user();

            $panel_data = [
                'is_logged_in' => is_user_logged_in(),
                'is_customer' => is_array($customer),
                'login_url' => wp_login_url(get_permalink()),
                'logout_url' => wp_logout_url(get_permalink()),
                'lost_password_url' => wp_lostpassword_url(get_permalink()),
                'login_nonce' => wp_create_nonce('bw_cp_login_nonce'),
                'booking_url' => self::get_booking_page_url(),
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('bw_cp_nonce'),
                'date_format' => $date_format,
                'time_format' => $time_format,
                'general' => $general,
                'payment' => isset($settings['payment']) && is_array($settings['payment']) ? $settings['payment'] : [],
                'appearance' => $appearance,
                'labels' => self::get_panel_labels(),
                'account_fields' => self::get_account_fields(),
                'profile' => self::format_profile_payload($customer, $user),
            ];

            wp_localize_script('bookwellab-customer-panel', 'bookwellab_cp_obj', apply_filters('bookwellab_customer_panel_script_data', $panel_data));
        }

        /**
         * @return string
         */
        private static function get_booking_page_url()
        {
            return BookWellAB_Default_Pages::get_resolved_page_url('booking');
        }

        /**
         * Resolve the frontend customer panel (My Booking) page URL.
         *
         * @return string
         */
        public static function get_panel_page_url()
        {
            return BookWellAB_Default_Pages::get_resolved_page_url('customer_panel');
        }

        /**
         * @return bool
         */
        private function should_load_assets()
        {
            if (is_admin()) {
                return false;
            }

            if (self::$shortcode_rendered) {
                return true;
            }

            global $post;
            return $post instanceof \WP_Post && has_shortcode($post->post_content, 'bookwellnow_customer_panel');
        }

        /**
         * @return array<string, string>
         */
        public static function get_panel_labels()
        {
            bookwellab_load_textdomain();

            $defaults = BookWellAB_Settings::get_customer_panel_label_defaults();

            $settings = BookWellAB_Settings::get_settings();
            $saved = isset($settings['customer_panel']) && is_array($settings['customer_panel'])
                ? $settings['customer_panel']
                : [];

            $labels = [];
            foreach ($defaults as $key => $default) {
                $value = isset($saved[$key]) ? trim((string) $saved[$key]) : '';
                $labels[$key] = $value !== '' ? $value : (string) $default;
            }

            return apply_filters('bookwellab_customer_panel_labels', $labels);
        }

        /**
         * Edit Account field configuration sourced from the Form Fields feature.
         *
         * Returns label/placeholder/error/required/hidden for the account fields
         * (first_name, last_name, email, phone) so the panel mirrors the booking
         * form field settings managed on the Form Fields admin page.
         *
         * @return array<string, array<string, mixed>>
         */
        public static function get_account_fields()
        {
            $panel_labels = self::get_panel_labels();
            $account_keys = ['first_name', 'last_name', 'email', 'phone'];

            $config = [];
            foreach ($account_keys as $key) {
                $config[$key] = [
                    'label' => isset($panel_labels[$key]) ? (string) $panel_labels[$key] : '',
                    'placeholder' => '',
                    'error_message' => '',
                    'required' => in_array($key, ['first_name', 'email'], true),
                    'hidden' => false,
                ];
            }

            if (class_exists('BookWellAB_Form_Fields')) {
                $fields = BookWellAB_Form_Fields::get_form_fields();
                foreach ($fields as $field) {
                    if (!is_array($field)) {
                        continue;
                    }
                    $name = isset($field['field_name']) ? (string) $field['field_name'] : '';
                    if (!isset($config[$name])) {
                        continue;
                    }

                    $label = isset($field['label']) ? trim((string) $field['label']) : '';
                    if ($label !== '') {
                        $config[$name]['label'] = $label;
                    }
                    $config[$name]['placeholder'] = isset($field['placeholder']) ? (string) $field['placeholder'] : '';
                    $config[$name]['error_message'] = isset($field['error_message']) ? (string) $field['error_message'] : '';
                    $config[$name]['required'] = !empty($field['is_required']);
                    // Email is always visible on the account form.
                    $config[$name]['hidden'] = ($name === 'email') ? false : !empty($field['is_hide']);
                }
            }

            return apply_filters('bookwellab_customer_panel_account_fields', $config);
        }

        /**
         * @param array<string, mixed>|null $customer Customer row.
         * @param \WP_User                  $user     Current user.
         * @return array<string, mixed>
         */
        private static function format_profile_payload($customer, $user)
        {
            if (!is_array($customer)) {
                return [
                    'first_name' => $user instanceof \WP_User ? (string) $user->first_name : '',
                    'last_name' => $user instanceof \WP_User ? (string) $user->last_name : '',
                    'email' => $user instanceof \WP_User ? (string) $user->user_email : '',
                    'phone_code' => '',
                    'phone' => '',
                    'display_name' => $user instanceof \WP_User ? (string) $user->display_name : '',
                ];
            }

            return [
                'first_name' => (string) ($customer['bw_first_name'] ?? ''),
                'last_name' => (string) ($customer['bw_last_name'] ?? ''),
                'email' => (string) ($customer['bw_email'] ?? ''),
                'phone_code' => (string) ($customer['bw_phone_code'] ?? ''),
                'phone' => (string) ($customer['bw_phone'] ?? ''),
                'display_name' => trim((string) ($customer['bw_first_name'] ?? '') . ' ' . (string) ($customer['bw_last_name'] ?? '')),
            ];
        }

        /**
         * Authenticate a customer from the embedded panel login form.
         *
         * @return void
         */
        public function ajax_login()
        {
            if (!check_ajax_referer('bw_cp_login_nonce', '_wpnonce', false)) {
                wp_send_json_error(['msg' => esc_html__('Security token mismatch. Please refresh the page.', 'bookwellnow-appointment-booking')]);
            }

            $labels = self::get_panel_labels();

            if (is_user_logged_in()) {
                wp_send_json_success(['msg' => (string) ($labels['login_success'] ?? '')]);
            }

            $username = sanitize_text_field(BookWellAB_Input::get_post_string('username'));
            // Password intentionally not sanitized; passed to wp_signon verbatim.
            $password = (string) BookWellAB_Input::get_post_string('password');
            $remember = BookWellAB_Input::get_post_string('remember') === '1';

            if ($username === '' || $password === '') {
                wp_send_json_error(['msg' => (string) ($labels['login_required_fields'] ?? esc_html__('Please enter your username and password.', 'bookwellnow-appointment-booking'))]);
            }

            // Allow login with email address.
            if (is_email($username)) {
                $user_by_email = get_user_by('email', $username);
                if ($user_by_email instanceof \WP_User) {
                    $username = $user_by_email->user_login;
                }
            }

            $user = wp_signon(
                [
                    'user_login' => $username,
                    'user_password' => $password,
                    'remember' => $remember,
                ],
                is_ssl()
            );

            if (is_wp_error($user)) {
                wp_send_json_error(['msg' => (string) ($labels['login_failed'] ?? esc_html__('Invalid username or password.', 'bookwellnow-appointment-booking'))]);
            }

            wp_set_current_user($user->ID);
            BookWellAB_Customer_Account::maybe_link_customer_by_email($user);

            wp_send_json_success(['msg' => (string) ($labels['login_success'] ?? esc_html__('Logged in successfully.', 'bookwellnow-appointment-booking'))]);
        }

        /**
         * Send a password reset email from the embedded panel forgot-password form.
         *
         * @return void
         */
        public function ajax_lost_password()
        {
            if (!check_ajax_referer('bw_cp_login_nonce', '_wpnonce', false)) {
                wp_send_json_error(['msg' => esc_html__('Security token mismatch. Please refresh the page.', 'bookwellnow-appointment-booking')]);
            }

            $labels = self::get_panel_labels();

            $user_login = sanitize_text_field(BookWellAB_Input::get_post_string('user_login'));
            if ($user_login === '') {
                wp_send_json_error(['msg' => (string) ($labels['forgot_required'] ?? esc_html__('Please enter your username or email address.', 'bookwellnow-appointment-booking'))]);
            }

            // Verify the account exists before attempting to send a reset link.
            $user = false;
            if (is_email($user_login)) {
                $user = get_user_by('email', $user_login);
            }
            if (!$user instanceof \WP_User) {
                $user = get_user_by('login', $user_login);
            }

            if (!$user instanceof \WP_User) {
                wp_send_json_error(['msg' => (string) ($labels['forgot_not_found'] ?? esc_html__('No account found with that username or email address.', 'bookwellnow-appointment-booking'))]);
            }

            $result = retrieve_password($user->user_login);

            if (is_wp_error($result)) {
                wp_send_json_error(['msg' => (string) ($labels['forgot_failed'] ?? esc_html__('Unable to send the password reset email. Please check the details and try again.', 'bookwellnow-appointment-booking'))]);
            }

            wp_send_json_success(['msg' => (string) ($labels['forgot_success'] ?? esc_html__('A password reset link has been emailed to you.', 'bookwellnow-appointment-booking'))]);
        }

        /**
         * @return void
         */
        public function ajax_get_appointments()
        {
            $this->verify_ajax_request();

            $customer = BookWellAB_Customer_Account::get_current_panel_customer();
            $user = wp_get_current_user();
            if (!$customer && (!$user instanceof \WP_User || $user->ID <= 0)) {
                wp_send_json_error(['msg' => esc_html__('Please log in to continue.', 'bookwellnow-appointment-booking')]);
            }

            $page = max(1, absint(BookWellAB_Input::get_post_string('page')));
            $per_page = 6;
            $search = sanitize_text_field(BookWellAB_Input::get_post_string('search'));
            $date_from = sanitize_text_field(BookWellAB_Input::get_post_string('date_from'));
            $date_to = sanitize_text_field(BookWellAB_Input::get_post_string('date_to'));

            global $wpdb;
            $customer_id = is_array($customer) ? (int) ($customer['bw_customer_id'] ?? 0) : 0;
            $customer_email = is_array($customer) ? sanitize_email((string) ($customer['bw_email'] ?? '')) : '';
            if ($customer_email === '' && $user instanceof \WP_User) {
                $customer_email = sanitize_email((string) $user->user_email);
            }

            // Allow any logged-in user: match by customer id when linked, otherwise by WP email.
            if ($customer_id <= 0 && $customer_email === '') {
                wp_send_json_success([
                    'items' => [],
                    'total' => 0,
                    'page' => $page,
                    'per_page' => $per_page,
                ]);
            }

            $offset = ($page - 1) * $per_page;
            $search_like = $search !== '' ? '%' . $wpdb->esc_like($search) . '%' : '';
            $email_filter = $customer_email;
            $date_from_filter = $date_from;
            $date_to_filter = $date_to;

            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Dynamic plugin-owned table name.
            $total = (int) $wpdb->get_var(
                $wpdb->prepare(
                    'SELECT COUNT(*) FROM `' . esc_sql($this->bwdb->get_table_name('appointments')) . '` WHERE
                    (
                        (%d > 0 AND bw_customer_id = %d)
                        OR (%d = 0 AND %s != "" AND LOWER(bw_customer_email) = LOWER(%s))
                    )
                    AND (
                        %s = ""
                        OR bw_customer_name LIKE %s
                        OR bw_customer_email LIKE %s
                        OR bw_customer_phone LIKE %s
                        OR bw_service_name LIKE %s
                        OR bw_staff_name LIKE %s
                        OR CAST(bw_appointment_id AS CHAR) LIKE %s
                    )
                    AND (%s = "" OR bw_date >= %s)
                    AND (%s = "" OR bw_date <= %s)',
                    $customer_id,
                    $customer_id,
                    $customer_id,
                    $email_filter,
                    $email_filter,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $date_from_filter,
                    $date_from_filter,
                    $date_to_filter,
                    $date_to_filter
                )
            );

            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Dynamic plugin-owned table name.
            $rows = $wpdb->get_results(
                $wpdb->prepare(
                    'SELECT * FROM `' . esc_sql($this->bwdb->get_table_name('appointments')) . '` WHERE
                    (
                        (%d > 0 AND bw_customer_id = %d)
                        OR (%d = 0 AND %s != "" AND LOWER(bw_customer_email) = LOWER(%s))
                    )
                    AND (
                        %s = ""
                        OR bw_customer_name LIKE %s
                        OR bw_customer_email LIKE %s
                        OR bw_customer_phone LIKE %s
                        OR bw_service_name LIKE %s
                        OR bw_staff_name LIKE %s
                        OR CAST(bw_appointment_id AS CHAR) LIKE %s
                    )
                    AND (%s = "" OR bw_date >= %s)
                    AND (%s = "" OR bw_date <= %s)
                    ORDER BY bw_appointment_id DESC LIMIT %d OFFSET %d',
                    $customer_id,
                    $customer_id,
                    $customer_id,
                    $email_filter,
                    $email_filter,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $search_like,
                    $date_from_filter,
                    $date_from_filter,
                    $date_to_filter,
                    $date_to_filter,
                    $per_page,
                    $offset
                ),
                ARRAY_A
            );

            $items = [];
            foreach ((array) $rows as $row) {
                $items[] = $this->format_appointment_row($row);
            }

            wp_send_json_success([
                'items' => $items,
                'total' => $total,
                'page' => $page,
                'per_page' => $per_page,
            ]);
        }

        /**
         * @return void
         */
        public function ajax_cancel_appointment()
        {
            $this->verify_ajax_request();

            $customer = BookWellAB_Customer_Account::get_current_panel_customer();
            if (!$customer) {
                wp_send_json_error(['msg' => esc_html__('Unable to create or find customer account.', 'bookwellnow-appointment-booking')]);
            }

            $appointment_id = absint(BookWellAB_Input::get_post_string('appointment_id'));
            $result = BookWellAB_Booking_Helper::cancel_appointment_for_customer($appointment_id, (int) $customer['bw_customer_id']);

            if (is_wp_error($result)) {
                wp_send_json_error(['msg' => $result->get_error_message()]);
            }

            wp_send_json_success(['msg' => esc_html__('Appointment cancelled successfully.', 'bookwellnow-appointment-booking')]);
        }

        /**
         * @return void
         */
        public function ajax_update_profile()
        {
            $this->verify_ajax_request();

            $customer = BookWellAB_Customer_Account::get_current_panel_customer();
            if (!$customer) {
                wp_send_json_error(['msg' => esc_html__('Unable to create or find customer account.', 'bookwellnow-appointment-booking')]);
            }

            $first_name = sanitize_text_field(BookWellAB_Input::get_post_string('first_name'));
            $last_name = sanitize_text_field(BookWellAB_Input::get_post_string('last_name'));
            $phone = preg_replace('/\D+/', '', (string) BookWellAB_Input::get_post_string('phone'));
            $phone_code = sanitize_text_field(BookWellAB_Input::get_post_string('phone_code'));

            $account_fields = self::get_account_fields();
            $values = [
                'first_name' => $first_name,
                'last_name' => $last_name,
                'phone' => $phone,
            ];
            foreach ($values as $field_key => $value) {
                $field = isset($account_fields[$field_key]) ? $account_fields[$field_key] : [];
                if (!empty($field['hidden'])) {
                    continue;
                }
                if (!empty($field['required']) && trim((string) $value) === '') {
                    $message = isset($field['error_message']) ? trim((string) $field['error_message']) : '';
                    if ($message === '') {
                        /* translators: %s: form field label. */
                        $message = sprintf(esc_html__('%s is required.', 'bookwellnow-appointment-booking'), (string) ($field['label'] ?? $field_key));
                    }
                    wp_send_json_error(['msg' => $message]);
                }
            }

            global $wpdb;
            $cust_table = esc_sql($this->bwdb->get_table_name('customers'));
            $customer_id = (int) $customer['bw_customer_id'];

            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Updating customer profile in plugin-owned table.
            $updated = $wpdb->update(
                $cust_table,
                [
                    'bw_first_name' => $first_name,
                    'bw_last_name' => $last_name,
                    'bw_phone' => $phone,
                    'bw_phone_code' => $phone_code,
                ],
                ['bw_customer_id' => $customer_id],
                ['%s', '%s', '%s', '%s'],
                ['%d']
            );

            if ($updated === false) {
                wp_send_json_error(['msg' => esc_html__('Unable to update profile.', 'bookwellnow-appointment-booking')]);
            }

            $wp_user_id = isset($customer['bw_wp_user_id']) ? absint($customer['bw_wp_user_id']) : 0;
            if ($wp_user_id > 0) {
                wp_update_user([
                    'ID' => $wp_user_id,
                    'first_name' => $first_name,
                    'last_name' => $last_name,
                    'display_name' => trim($first_name . ' ' . $last_name),
                ]);
            }

            wp_send_json_success([
                'msg' => esc_html__('Profile updated successfully.', 'bookwellnow-appointment-booking'),
                'profile' => [
                    'first_name' => $first_name,
                    'last_name' => $last_name,
                    'email' => (string) ($customer['bw_email'] ?? ''),
                    'phone_code' => $phone_code,
                    'phone' => $phone,
                    'display_name' => trim($first_name . ' ' . $last_name),
                ],
            ]);
        }

        /**
         * @return void
         */
        public function ajax_change_password()
        {
            $this->verify_ajax_request();

            $user = wp_get_current_user();
            if (!$user instanceof \WP_User || $user->ID <= 0) {
                wp_send_json_error(['msg' => esc_html__('Unauthorized', 'bookwellnow-appointment-booking')]);
            }

            $current_password = (string) BookWellAB_Input::get_post_string('current_password');
            $new_password = (string) BookWellAB_Input::get_post_string('new_password');
            $confirm_password = (string) BookWellAB_Input::get_post_string('confirm_password');

            if ($new_password === '' || strlen($new_password) < 8) {
                wp_send_json_error(['msg' => esc_html__('New password must be at least 8 characters.', 'bookwellnow-appointment-booking')]);
            }

            if ($new_password !== $confirm_password) {
                wp_send_json_error(['msg' => esc_html__('Passwords do not match.', 'bookwellnow-appointment-booking')]);
            }

            if (!wp_check_password($current_password, $user->user_pass, $user->ID)) {
                wp_send_json_error(['msg' => esc_html__('Current password is incorrect.', 'bookwellnow-appointment-booking')]);
            }

            wp_set_password($new_password, $user->ID);
            wp_set_auth_cookie($user->ID);

            wp_send_json_success(['msg' => esc_html__('Password updated successfully.', 'bookwellnow-appointment-booking')]);
        }

        /**
         * @return void
         */
        private function verify_ajax_request()
        {
            if (!is_user_logged_in()) {
                wp_send_json_error(['msg' => esc_html__('Please log in to continue.', 'bookwellnow-appointment-booking')]);
            }

            if (!check_ajax_referer('bw_cp_nonce', '_wpnonce', false)) {
                wp_send_json_error(['msg' => esc_html__('Security token mismatch.', 'bookwellnow-appointment-booking')]);
            }
        }

        /**
         * @param array<string, mixed> $row Appointment row.
         * @return array<string, mixed>
         */
        private function format_appointment_row(array $row)
        {
            $settings = BookWellAB_Settings::get_settings();
            $date_format = isset($settings['general']['date_format']) ? (string) $settings['general']['date_format'] : 'Y-m-d';
            $time_format = isset($settings['general']['time_format']) ? (string) $settings['general']['time_format'] : 'H:i';
            if ($date_format === 'inherit') {
                $date_format = (string) get_option('date_format');
            }
            if ($time_format === 'inherit') {
                $time_format = (string) get_option('time_format');
            }

            $date = isset($row['bw_date']) ? (string) $row['bw_date'] : '';
            $time = isset($row['bw_time']) ? (string) $row['bw_time'] : '';
            $duration = isset($row['bw_duration']) ? (int) $row['bw_duration'] : 0;
            $timezone = wp_timezone();
            $timestamp = BookWellAB_Booking_Helper::to_site_timestamp($date, $time);

            $formatted_date = $timestamp ? wp_date($date_format . ' ' . $time_format, $timestamp, $timezone) : '';
            $date_only = $timestamp ? wp_date($date_format, $timestamp, $timezone) : '';
            $time_label = $timestamp ? wp_date($time_format, $timestamp, $timezone) : '';
            $end_timestamp = $timestamp ? $timestamp + ($duration * 60) : false;
            $time_range = '';
            if ($timestamp && $end_timestamp) {
                $time_range = wp_date($time_format, $timestamp, $timezone) . ' – ' . wp_date($time_format, $end_timestamp, $timezone);
            }

            $status = isset($row['bw_status']) ? strtolower(trim((string) $row['bw_status'])) : 'pending';
            $payment_status = isset($row['bw_payment_status']) ? strtolower(trim((string) $row['bw_payment_status'])) : 'pending';
            $gateway = isset($row['bw_payment_gateway']) ? sanitize_key((string) $row['bw_payment_gateway']) : 'offline';
            $service_name = BookWellAB_Input::decode_entities_for_display((string) ($row['bw_service_name'] ?? ''));
            if ($service_name === '' && !empty($row['bw_service_id'])) {
                global $wpdb;
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Dynamic plugin-owned table name.
                $service_name = (string) $wpdb->get_var(
                    $wpdb->prepare(
                        'SELECT bw_service_name FROM `' . esc_sql($this->bwdb->get_table_name('services')) . '` WHERE bw_service_id = %d',
                        (int) $row['bw_service_id']
                    )
                );
                $service_name = BookWellAB_Input::decode_entities_for_display($service_name);
            }

            return [
                'id' => (int) ($row['bw_appointment_id'] ?? 0),
                'service_name' => $service_name,
                'staff_name' => (string) ($row['bw_staff_name'] ?? ''),
                'date_label' => $formatted_date,
                'date_only_label' => $date_only,
                'time_label' => $time_label,
                'time_range' => $time_range,
                'duration_label' => self::format_duration_label($duration),
                'status' => $status,
                'status_label' => self::format_status_label($status),
                'payment_status' => $payment_status,
                'payment_status_label' => self::format_status_label($payment_status),
                'payment_gateway' => $gateway,
                'payment_gateway_label' => self::format_payment_gateway_label($gateway),
                'price' => (float) ($row['bw_price'] ?? 0),
                'price_label' => self::format_price((float) ($row['bw_price'] ?? 0)),
                'can_cancel' => in_array($status, ['approved', 'pending'], true),
                'zoom_join_url' => ($status === 'approved' && !empty($row['bw_zoom_join_url']))
                    ? esc_url((string) $row['bw_zoom_join_url'])
                    : '',
                'extra_meeting_links' => [],
            ];

            /**
             * Filters a customer-panel appointment list item after formatting.
             *
             * @param array<string, mixed> $item Formatted appointment item.
             * @param array<string, mixed> $row  Raw appointment database row.
             */
            return apply_filters('bookwellab_customer_panel_appointment_item', $item, $row);
        }

        /**
         * @param int $minutes Duration in minutes.
         * @return string
         */
        private static function format_duration_label($minutes)
        {
            $minutes = max(0, (int) $minutes);
            $hours = intdiv($minutes, 60);
            $mins = $minutes % 60;
            $parts = [];

            if ($hours > 0) {
                $parts[] = sprintf(
                    /* translators: %d: number of hours. */
                    _n('%d Hour', '%d Hours', $hours, 'bookwellnow-appointment-booking'),
                    $hours
                );
            }
            if ($mins > 0) {
                $parts[] = sprintf(
                    /* translators: %d: number of minutes. */
                    _n('%d Min', '%d Mins', $mins, 'bookwellnow-appointment-booking'),
                    $mins
                );
            }

            return !empty($parts) ? implode(' ', $parts) : __('0 Mins', 'bookwellnow-appointment-booking');
        }

        /**
         * @param string $status Status slug.
         * @return string
         */
        private static function format_status_label($status)
        {
            $status = strtolower(trim($status));
            $map = [
                'pending' => __('Pending', 'bookwellnow-appointment-booking'),
                'approved' => __('Approved', 'bookwellnow-appointment-booking'),
                'cancelled' => __('Cancelled', 'bookwellnow-appointment-booking'),
                'canceled' => __('Cancelled', 'bookwellnow-appointment-booking'),
                'completed' => __('Completed', 'bookwellnow-appointment-booking'),
                'paid' => __('Paid', 'bookwellnow-appointment-booking'),
                'failed' => __('Failed', 'bookwellnow-appointment-booking'),
            ];

            return isset($map[$status]) ? $map[$status] : ucfirst($status);
        }

        /**
         * @param string $gateway Payment gateway slug.
         * @return string
         */
        private static function format_payment_gateway_label($gateway)
        {
            $settings = BookWellAB_Settings::get_settings();
            $payment = isset($settings['payment']) && is_array($settings['payment']) ? $settings['payment'] : [];

            $map = [
                'offline' => isset($payment['offline_label']) ? (string) $payment['offline_label'] : __('Pay on Arrival', 'bookwellnow-appointment-booking'),
                'manually' => isset($payment['manually_label']) ? (string) $payment['manually_label'] : __('Manually', 'bookwellnow-appointment-booking'),
                'paypal' => isset($payment['paypal_label']) ? (string) $payment['paypal_label'] : __('PayPal', 'bookwellnow-appointment-booking'),
            ];

            return isset($map[$gateway]) ? $map[$gateway] : ucfirst($gateway);
        }

        /**
         * @param float $price Price amount.
         * @return string
         */
        private static function format_price($price)
        {
            $settings = BookWellAB_Settings::get_settings();
            $general = isset($settings['general']) && is_array($settings['general']) ? $settings['general'] : [];
            $currency_code = isset($general['currency_code']) ? (string) $general['currency_code'] : 'USD';
            $position = isset($general['currency_position']) ? (string) $general['currency_position'] : 'before';
            $currencies = BookWellAB_Settings::get_currencies();
            $symbol = isset($currencies[$currency_code]['symbol']) ? $currencies[$currency_code]['symbol'] : '$';
            $formatted = number_format_i18n((float) $price, 2);

            return $position === 'after' ? $formatted . $symbol : $symbol . $formatted;
        }
    }
}
