<?php
/**
 * Sign in with Apple for customer accounts.
 * Uses Apple's stable subject identifier; email is optional and never the
 * primary identity key because customers may use Private Email Relay.
 */
if ( ! defined( 'ABSPATH' ) ) { exit; }

class YATOON_Apple_Identity {
    const STATE_TTL = 10 * MINUTE_IN_SECONDS;

    public static function init() {
        add_action( 'admin_post_nopriv_yatoon_apple_identity_start', array( __CLASS__, 'start' ) );
        add_action( 'admin_post_yatoon_apple_identity_start', array( __CLASS__, 'start' ) );
        add_action( 'admin_post_nopriv_yatoon_apple_identity_callback', array( __CLASS__, 'callback' ) );
        add_action( 'admin_post_yatoon_apple_identity_callback', array( __CLASS__, 'callback' ) );
        add_action( 'yatoon_booking_created', array( __CLASS__, 'reconcile_customer_link' ), 121 );
    }

    public static function callback_url() {
        return admin_url( 'admin-post.php?action=yatoon_apple_identity_callback' );
    }

    private static function service_id() { return trim( (string) get_option( 'yatoon_apple_service_id', get_option( 'yatoon_apple_client_id', '' ) ) ); }
    private static function team_id() { return trim( (string) get_option( 'yatoon_apple_team_id', '' ) ); }
    private static function key_id() { return trim( (string) get_option( 'yatoon_apple_key_id', '' ) ); }
    private static function private_key() { return class_exists( 'YATOON_Crypto' ) ? trim( (string) YATOON_Crypto::get_option( 'yatoon_apple_private_key', '' ) ) : ''; }

    public static function ready() {
        return is_ssl() && function_exists( 'openssl_sign' ) && self::service_id() && self::team_id() && self::key_id() && self::private_key();
    }

    public static function enabled() {
        return class_exists( 'YATOON_Customer_Portal' ) && '1' === (string) get_option( 'yatoon_apple_identity_enabled', '0' ) && self::ready();
    }

    public static function start_url( $return_url ) {
        if ( ! self::enabled() ) { return ''; }
        return add_query_arg( array(
            'action' => 'yatoon_apple_identity_start',
            'return_url' => rawurlencode( self::safe_return_url( $return_url ) ),
        ), admin_url( 'admin-post.php' ) );
    }

    public static function button_icon() {
        return '<span class="sbs-apple-signin-mark" aria-hidden="true"><svg width="17" height="20" style="display:block;width:17px!important;height:20px!important;max-width:17px!important;max-height:20px!important" viewBox="0 0 17 20" focusable="false"><path fill="currentColor" d="M14.18 10.57c.02-2.19 1.79-3.24 1.87-3.29a4.02 4.02 0 0 0-3.17-1.72c-1.33-.14-2.63.8-3.31.8-.7 0-1.75-.79-2.89-.76a4.2 4.2 0 0 0-3.54 2.16c-1.53 2.64-.39 6.52 1.08 8.65.74 1.04 1.6 2.2 2.74 2.16 1.11-.05 1.53-.7 2.87-.7 1.33 0 1.72.7 2.88.68 1.2-.02 1.95-1.04 2.66-2.09a8.64 8.64 0 0 0 1.22-2.48 3.79 3.79 0 0 1-2.41-3.41ZM12 4.15A3.83 3.83 0 0 0 12.88 1a3.92 3.92 0 0 0-2.54 1.5 3.66 3.66 0 0 0-.9 3.05A3.24 3.24 0 0 0 12 4.15Z"/></svg></span>';
    }

    /** Keep the guest-booking choice clear without repeating the menu options below. */
    public static function customer_banner_subtext( $portal_url = '' ) {
        return __( 'Sign in to manage appointments, or continue as guest.', 'yatoon-booking-system' );
    }

    /** Upgrade the original returning-customer-only copy without replacing merchant custom text. */
    public static function customer_banner_prompt() {
        $prompt = trim( (string) get_option( 'yatoon_portal_banner_prompt', '' ) );
        // Case-insensitive: older releases shipped this default with inconsistent
        // capitalization ("Already booked with Us?"), which a strict === match missed,
        // leaving sites stuck on the old copy forever.
        if ( '' === $prompt || 0 === strcasecmp( $prompt, 'Already booked with us?' ) ) {
            return __( 'Your appointments', 'yatoon-booking-system' );
        }
        return $prompt;
    }

    /** Use an entry label that also makes sense to first-time customers. */
    public static function customer_signin_label() {
        $label = trim( (string) get_option( 'yatoon_portal_signin_label', '' ) );
        if ( '' === $label || 'View or manage appointments' === $label ) {
            return __( 'Sign in or create account', 'yatoon-booking-system' );
        }
        return $label;
    }

    /**
     * Editable wording for the generic email-code sign-in link.
     * Google/Apple/Facebook keep their required brand names and stay hardcoded;
     * this one has no brand attached, so merchants can simplify or translate it.
     */
    public static function email_signin_label() {
        $label = trim( (string) get_option( 'yatoon_portal_email_signin_label', '' ) );
        return '' === $label ? __( 'Continue with email', 'yatoon-booking-system' ) : $label;
    }

    /** Editable wording for the generic text-message-code sign-in link. */
    public static function sms_signin_label() {
        $label = trim( (string) get_option( 'yatoon_portal_sms_signin_label', '' ) );
        return '' === $label ? __( 'Continue with text message', 'yatoon-booking-system' ) : $label;
    }

    /** Booking-page dropdown that keeps every sign-in route in one place. */
    public static function customer_menu_html( $return_url, $portal_url = '', $wide = false ) {
        $return_url = self::safe_return_url( $return_url );
        $google = class_exists( 'YATOON_Google_Identity' ) ? YATOON_Google_Identity::start_url( 'customer', $return_url ) : '';
        $apple = self::start_url( $return_url );
        $facebook = class_exists( 'YATOON_Facebook_Identity' ) ? YATOON_Facebook_Identity::start_url( $return_url ) : '';
        $email = '1' === (string) get_option( 'yatoon_portal_email_login_enabled', '1' ) && $portal_url;
        $sms = '1' === (string) get_option( 'yatoon_portal_sms_login_enabled', '1' ) && $portal_url && class_exists( 'YATOON_Twilio' ) && YATOON_Twilio::is_enabled();
        if ( ! $google && ! $apple && ! $facebook && ! $email && ! $sms ) { return ''; }
        $trigger_label = self::customer_signin_label();
        $trigger_bg = sanitize_hex_color( get_option( 'yatoon_portal_signin_bg', '#f8fafc' ) ) ?: '#f8fafc';
        $trigger_text = sanitize_hex_color( get_option( 'yatoon_portal_signin_text', '#1f2937' ) ) ?: '#1f2937';
        $panel_bg = sanitize_hex_color( get_option( 'yatoon_portal_signin_panel_bg', '#ffffff' ) ) ?: '#ffffff';
        $menu_style = sprintf( '--sbs-signin-trigger-bg:%s;--sbs-signin-trigger-text:%s;--sbs-signin-panel-bg:%s', $trigger_bg, $trigger_text, $panel_bg );
        $portal_args = array( 'yatoon_return' => rawurlencode( $return_url ) );

        static $interaction_added = false;
        if ( ! $interaction_added && wp_script_is( 'sbs-frontend-js', 'enqueued' ) ) {
            $interaction_added = true;
            wp_add_inline_script( 'sbs-frontend-js', <<<'JS'
(function(){
    if(window.yatoonSigninMenuReady){return;} window.yatoonSigninMenuReady=true;
    function menus(){return document.querySelectorAll('.sbs-signin-menu');}
    function setOpen(menu,open){
        if(menu._yatoonCloseTimer){window.clearTimeout(menu._yatoonCloseTimer);menu._yatoonCloseTimer=0;}
        menu.classList.toggle('is-open',open);
        var trigger=menu.querySelector('.sbs-signin-trigger');
        if(trigger){trigger.setAttribute('aria-expanded',open?'true':'false');}
    }
    function closeOthers(keep){menus().forEach(function(menu){if(menu!==keep){setOpen(menu,false);}});}
    document.addEventListener('click',function(event){
        var menu=event.target.closest&&event.target.closest('.sbs-signin-menu');
        var trigger=event.target.closest&&event.target.closest('.sbs-signin-trigger');
        if(trigger&&menu){event.preventDefault();var willOpen=!menu.classList.contains('is-open');closeOthers(menu);setOpen(menu,willOpen);return;}
        if(!menu){closeOthers(null);}
    });
    var fineHover=window.matchMedia&&window.matchMedia('(hover: hover) and (pointer: fine)');
    document.addEventListener('pointerover',function(event){
        if(!fineHover||!fineHover.matches){return;}
        var menu=event.target.closest&&event.target.closest('.sbs-signin-menu');
        if(!menu||(event.relatedTarget&&menu.contains(event.relatedTarget))){return;}
        closeOthers(menu);setOpen(menu,true);
    });
    document.addEventListener('pointerout',function(event){
        if(!fineHover||!fineHover.matches){return;}
        var menu=event.target.closest&&event.target.closest('.sbs-signin-menu');
        if(!menu||(event.relatedTarget&&menu.contains(event.relatedTarget))){return;}
        menu._yatoonCloseTimer=window.setTimeout(function(){setOpen(menu,false);},220);
    });
    document.addEventListener('keydown',function(event){if(event.key==='Escape'){menus().forEach(function(menu){if(menu.classList.contains('is-open')){setOpen(menu,false);var trigger=menu.querySelector('.sbs-signin-trigger');if(trigger){trigger.focus();}}});}});
})();
JS
            );
        }
        ob_start();
        ?>
        <div class="sbs-signin-menu<?php echo $wide ? ' sbs-signin-menu-wide' : ''; ?>" style="<?php echo esc_attr( $menu_style ); ?>">
            <button type="button" class="sbs-signin-trigger" aria-expanded="false">
                <span class="sbs-signin-trigger-icon" aria-hidden="true"><svg width="20" height="20" viewBox="0 0 24 24" focusable="false"><rect x="3" y="5" width="18" height="16" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M7 3v4M17 3v4M3 10h18M8 15l2.2 2.2L16 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
                <span class="sbs-signin-trigger-label"><?php echo esc_html( $trigger_label ); ?></span>
                <span class="sbs-signin-chevron" aria-hidden="true"><svg width="18" height="18" viewBox="0 0 20 20" focusable="false"><path d="m6 8 4 4 4-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
            </button>
            <div class="sbs-signin-options">
                <div class="sbs-signin-options-title"><?php esc_html_e( 'Sign in securely', 'yatoon-booking-system' ); ?></div>
                <?php if ( $google ) : ?><a href="<?php echo esc_url( $google ); ?>" class="sbs-google-signin"><?php echo YATOON_Google_Identity::button_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted static SVG. ?><span><?php esc_html_e( 'Continue with Google', 'yatoon-booking-system' ); ?></span></a><?php endif; ?>
                <?php if ( $apple ) : ?><a href="<?php echo esc_url( $apple ); ?>" class="sbs-apple-signin"><?php echo self::button_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted static SVG. ?><span><?php esc_html_e( 'Continue with Apple', 'yatoon-booking-system' ); ?></span></a><?php endif; ?>
                <?php if ( $facebook ) : ?><a href="<?php echo esc_url( $facebook ); ?>" class="sbs-facebook-signin"><?php echo YATOON_Facebook_Identity::button_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted static SVG. ?><span><?php esc_html_e( 'Continue with Facebook', 'yatoon-booking-system' ); ?></span></a><?php endif; ?>
                <?php if ( $email ) : ?><a href="<?php echo esc_url( add_query_arg( array_merge( $portal_args, array( 'yatoon_auth' => 'email' ) ), $portal_url ) ); ?>" class="sbs-code-signin"><span class="sbs-code-signin-icon" aria-hidden="true">&#9993;</span><span><?php echo esc_html( self::email_signin_label() ); ?></span></a><?php endif; ?>
                <?php if ( $sms ) : ?><a href="<?php echo esc_url( add_query_arg( array_merge( $portal_args, array( 'yatoon_auth' => 'sms' ) ), $portal_url ) ); ?>" class="sbs-code-signin"><span class="sbs-code-signin-icon" aria-hidden="true">&#9679;</span><span><?php echo esc_html( self::sms_signin_label() ); ?></span></a><?php endif; ?>
                <div class="sbs-signin-return-note"><?php esc_html_e( 'You will return here after signing in.', 'yatoon-booking-system' ); ?></div>
            </div>
            <?php if ( $wide ) : ?><div class="sbs-signin-optional-note"><?php esc_html_e( 'Optional — continue as guest.', 'yatoon-booking-system' ); ?></div><?php endif; ?>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    public static function save_settings_from_request() {
        foreach ( array( 'yatoon_apple_identity_enabled' ) as $key ) {
            update_option( $key, isset( $_POST[ $key ] ) ? '1' : '0', false );
        }
        $service_id = sanitize_text_field( wp_unslash( $_POST['yatoon_apple_service_id'] ?? $_POST['yatoon_apple_client_id'] ?? '' ) );
        update_option( 'yatoon_apple_service_id', $service_id, false );
        update_option( 'yatoon_apple_client_id', $service_id, false );
        update_option( 'yatoon_apple_team_id', sanitize_text_field( wp_unslash( $_POST['yatoon_apple_team_id'] ?? '' ) ), false );
        update_option( 'yatoon_apple_key_id', sanitize_text_field( wp_unslash( $_POST['yatoon_apple_key_id'] ?? '' ) ), false );
        $private_key = trim( (string) wp_unslash( $_POST['yatoon_apple_private_key'] ?? '' ) );
        if ( $private_key && class_exists( 'YATOON_Crypto' ) ) { YATOON_Crypto::update_option( 'yatoon_apple_private_key', $private_key ); }
    }

    public static function render_settings_rows() {
        $site_domain = (string) wp_parse_url( home_url( '/' ), PHP_URL_HOST );
        ?>
        <tr><th><?php esc_html_e( 'Apple sign-in', 'yatoon-booking-system' ); ?></th><td><label><input type="checkbox" name="yatoon_apple_identity_enabled" value="1" <?php checked( get_option( 'yatoon_apple_identity_enabled', '0' ), '1' ); ?>> <?php esc_html_e( 'Allow customers to sign in with Apple', 'yatoon-booking-system' ); ?></label><p class="description"><?php esc_html_e( 'Apple sign-in appears on My Appointments after all credentials below are saved. HTTPS is required.', 'yatoon-booking-system' ); ?></p>
        <details class="yatoon-identity-guide" <?php echo self::ready() ? '' : 'open'; ?>><summary><?php esc_html_e( 'Apple setup guide — required account, IDs, and private key', 'yatoon-booking-system' ); ?></summary><div class="yatoon-identity-guide-body">
        <p class="yatoon-identity-warning"><strong><?php esc_html_e( 'Before you start:', 'yatoon-booking-system' ); ?></strong> <?php esc_html_e( 'Sign in with Apple for a website requires Apple Developer Program access and a primary App ID with Sign in with Apple enabled. Apple then associates your website Services ID and private key with that primary App ID.', 'yatoon-booking-system' ); ?></p>
        <div class="yatoon-identity-guide-actions"><a class="button button-primary" href="https://developer.apple.com/account/resources/identifiers/list" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Open Apple Identifiers', 'yatoon-booking-system' ); ?> ↗</a><a class="button" href="https://developer.apple.com/account/resources/authkeys/list" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Open Apple Keys', 'yatoon-booking-system' ); ?> ↗</a><a class="button" href="https://developer.apple.com/help/account/capabilities/configure-sign-in-with-apple-for-the-web/" target="_blank" rel="noopener noreferrer"><?php esc_html_e( 'Official Apple instructions', 'yatoon-booking-system' ); ?> ↗</a></div>
        <ol><li><?php esc_html_e( 'In Certificates, Identifiers & Profiles, create or select the primary App ID and enable Sign in with Apple.', 'yatoon-booking-system' ); ?></li><li><?php esc_html_e( 'Create a Services ID for this website. Open it, enable Sign in with Apple, choose Configure, and associate it with the primary App ID.', 'yatoon-booking-system' ); ?></li><li><?php echo esc_html( sprintf( __( 'In Website URLs, enter the domain %s and add the exact HTTPS Return URL shown below.', 'yatoon-booking-system' ), $site_domain ) ); ?></li><li><?php esc_html_e( 'Under Keys, create a key with Sign in with Apple enabled and associate it with the same primary App ID. Download the .p8 file immediately—Apple only permits downloading it once—and record its Key ID.', 'yatoon-booking-system' ); ?></li><li><?php esc_html_e( 'Find your Team ID in Apple Developer membership details. Enter the Services ID, Team ID, Key ID, and complete .p8 private-key contents below, enable Apple sign-in, then save changes.', 'yatoon-booking-system' ); ?></li></ol>
        <p><strong><?php esc_html_e( 'Domain to register:', 'yatoon-booking-system' ); ?></strong></p><div class="yatoon-identity-copy"><input type="text" readonly value="<?php echo esc_attr( $site_domain ); ?>" aria-label="<?php esc_attr_e( 'Apple website domain', 'yatoon-booking-system' ); ?>"><button type="button" class="button yatoon-copy-identity-value"><?php esc_html_e( 'Copy domain', 'yatoon-booking-system' ); ?></button></div>
        <p><strong><?php esc_html_e( 'Return URL to register:', 'yatoon-booking-system' ); ?></strong></p><div class="yatoon-identity-copy"><input type="text" readonly value="<?php echo esc_attr( self::callback_url() ); ?>" aria-label="<?php esc_attr_e( 'Apple return URL', 'yatoon-booking-system' ); ?>"><button type="button" class="button yatoon-copy-identity-value"><?php esc_html_e( 'Copy return URL', 'yatoon-booking-system' ); ?></button></div>
        <p class="yatoon-identity-tip"><?php esc_html_e( 'Use the Services ID—not the App ID—as the Apple client identifier in this plugin. Paste the entire private key, including the BEGIN and END PRIVATE KEY lines.', 'yatoon-booking-system' ); ?></p>
        </div></details></td></tr>
        <tr><th><?php esc_html_e( 'Apple Services ID', 'yatoon-booking-system' ); ?></th><td><input class="regular-text" name="yatoon_apple_service_id" value="<?php echo esc_attr( self::service_id() ); ?>"><p class="description"><?php esc_html_e( 'The Services ID configured for Sign in with Apple on the web.', 'yatoon-booking-system' ); ?></p></td></tr>
        <tr><th><?php esc_html_e( 'Apple Team ID', 'yatoon-booking-system' ); ?></th><td><input class="regular-text" name="yatoon_apple_team_id" value="<?php echo esc_attr( self::team_id() ); ?>"></td></tr>
        <tr><th><?php esc_html_e( 'Apple Key ID', 'yatoon-booking-system' ); ?></th><td><input class="regular-text" name="yatoon_apple_key_id" value="<?php echo esc_attr( self::key_id() ); ?>"></td></tr>
        <tr><th><?php esc_html_e( 'Apple private key', 'yatoon-booking-system' ); ?></th><td><textarea class="large-text code" rows="5" name="yatoon_apple_private_key" autocomplete="new-password" placeholder="-----BEGIN PRIVATE KEY-----"></textarea><p class="description"><?php esc_html_e( 'Paste the .p8 private key. Leave blank to keep the saved encrypted key.', 'yatoon-booking-system' ); ?></p></td></tr>
        <tr><th><?php esc_html_e( 'Apple return URL', 'yatoon-booking-system' ); ?></th><td><code><?php echo esc_html( self::callback_url() ); ?></code><p class="description"><?php esc_html_e( 'Add this exact HTTPS URL to the Return URLs for your Apple Services ID.', 'yatoon-booking-system' ); ?></p></td></tr>
        <?php
    }

    public static function start() {
        if ( ! self::enabled() ) { wp_die( esc_html__( 'Apple sign-in is not configured.', 'yatoon-booking-system' ), 400 ); }
        $state = wp_generate_password( 48, false, false );
        $nonce = wp_generate_password( 48, false, false );
        $return_url = self::safe_return_url( wp_unslash( $_GET['return_url'] ?? home_url( '/' ) ) );
        set_transient( 'yatoon_apple_state_' . hash( 'sha256', $state ), array(
            'nonce' => $nonce, 'return_url' => $return_url, 'fingerprint' => self::fingerprint(),
        ), self::STATE_TTL );
        setcookie( 'yatoon_apple_state', hash_hmac( 'sha256', $state, wp_salt( 'nonce' ) ), array(
            'expires' => time() + self::STATE_TTL, 'path' => COOKIEPATH ? COOKIEPATH : '/',
            'domain' => COOKIE_DOMAIN ? COOKIE_DOMAIN : '', 'secure' => true, 'httponly' => true, 'samesite' => 'None',
        ) );
        $url = add_query_arg( array(
            'client_id' => self::service_id(), 'redirect_uri' => self::callback_url(),
            'response_type' => 'code id_token', 'response_mode' => 'form_post',
            'scope' => 'name email', 'state' => $state, 'nonce' => $nonce,
        ), 'https://appleid.apple.com/auth/authorize' );
        wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- fixed Apple endpoint.
        exit;
    }

    public static function callback() {
        $state = sanitize_text_field( wp_unslash( $_POST['state'] ?? '' ) );
        $key = 'yatoon_apple_state_' . hash( 'sha256', $state );
        $pending = $state ? get_transient( $key ) : false;
        delete_transient( $key );
        $cookie = sanitize_text_field( wp_unslash( $_COOKIE['yatoon_apple_state'] ?? '' ) );
        setcookie( 'yatoon_apple_state', '', array( 'expires' => time() - HOUR_IN_SECONDS, 'path' => COOKIEPATH ? COOKIEPATH : '/', 'domain' => COOKIE_DOMAIN ? COOKIE_DOMAIN : '', 'secure' => true, 'httponly' => true, 'samesite' => 'None' ) );
        $expected = $state ? hash_hmac( 'sha256', $state, wp_salt( 'nonce' ) ) : '';
        if ( ! is_array( $pending ) || ! $cookie || ! hash_equals( $expected, $cookie ) || ! hash_equals( (string) ( $pending['fingerprint'] ?? '' ), self::fingerprint() ) ) {
            self::redirect_error( home_url( '/' ), 'invalid_state' );
        }
        $return_url = self::safe_return_url( $pending['return_url'] ?? home_url( '/' ) );
        if ( isset( $_POST['error'] ) ) { self::redirect_error( $return_url, 'access_denied' ); }
        $client_secret = self::client_secret_jwt();
        if ( is_wp_error( $client_secret ) ) { self::redirect_error( $return_url, $client_secret->get_error_code() ); }
        $posted_token = sanitize_text_field( wp_unslash( $_POST['id_token'] ?? '' ) );
        $response = wp_remote_post( 'https://appleid.apple.com/auth/token', array( 'timeout' => 20, 'body' => array(
            'client_id' => self::service_id(), 'client_secret' => $client_secret,
            'code' => sanitize_text_field( wp_unslash( $_POST['code'] ?? '' ) ),
            'grant_type' => 'authorization_code', 'redirect_uri' => self::callback_url(),
        ) ) );
        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { self::redirect_error( $return_url, 'token_exchange' ); }
        $tokens = json_decode( wp_remote_retrieve_body( $response ), true );
        if ( ! $posted_token || empty( $tokens['id_token'] ) || ! hash_equals( $posted_token, (string) $tokens['id_token'] ) ) { self::redirect_error( $return_url, 'token_mismatch' ); }
        $claims = self::validated_claims( $tokens['id_token'] ?? '', $pending['nonce'] ?? '' );
        if ( is_wp_error( $claims ) ) { self::redirect_error( $return_url, $claims->get_error_code() ); }
        $first_user = json_decode( (string) wp_unslash( $_POST['user'] ?? '' ), true );
        if ( is_array( $first_user ) ) {
            $first = sanitize_text_field( $first_user['name']['firstName'] ?? '' );
            $last = sanitize_text_field( $first_user['name']['lastName'] ?? '' );
            $claims['name'] = trim( $first . ' ' . $last );
        }
        if ( ! class_exists( 'YATOON_Customer_Portal' ) || ! YATOON_Customer_Portal::get_instance() ) { self::redirect_error( $return_url, 'portal_unavailable' ); }
        $customer = YATOON_Customer_Portal::get_instance()->login_from_apple( $claims );
        if ( is_wp_error( $customer ) ) { self::redirect_error( $return_url, $customer->get_error_code() ); }
        self::remember_link( absint( $customer['local_client_id'] ?? 0 ), $claims, sanitize_text_field( $customer['square_id'] ?? '' ) );
        $handoff = YATOON_Customer_Portal::get_instance()->create_login_handoff( $customer );
        $redirect_args = array( 'yatoon_apple_login' => 'success' );
        if ( $handoff ) { $redirect_args['yatoon_customer_handoff'] = rawurlencode( $handoff ); }
        wp_safe_redirect( add_query_arg( $redirect_args, $return_url ) );
        exit;
    }

    private static function client_secret_jwt() {
        $header = self::base64url_encode( wp_json_encode( array( 'alg' => 'ES256', 'kid' => self::key_id() ) ) );
        $now = time();
        $payload = self::base64url_encode( wp_json_encode( array(
            'iss' => self::team_id(), 'iat' => $now, 'exp' => $now + HOUR_IN_SECONDS,
            'aud' => 'https://appleid.apple.com', 'sub' => self::service_id(),
        ) ) );
        $input = $header . '.' . $payload;
        $key = openssl_pkey_get_private( self::private_key() );
        if ( ! $key || ! openssl_sign( $input, $der, $key, OPENSSL_ALGO_SHA256 ) ) { return new WP_Error( 'apple_private_key' ); }
        $signature = self::der_to_jose( $der );
        return $signature ? $input . '.' . self::base64url_encode( $signature ) : new WP_Error( 'apple_private_key' );
    }

    private static function validated_claims( $jwt, $nonce ) {
        $parts = explode( '.', (string) $jwt );
        if ( 3 !== count( $parts ) ) { return new WP_Error( 'invalid_id_token' ); }
        $header = json_decode( self::base64url_decode( $parts[0] ), true );
        $payload = json_decode( self::base64url_decode( $parts[1] ), true );
        if ( ! is_array( $header ) || ! is_array( $payload ) || 'RS256' !== ( $header['alg'] ?? '' ) || empty( $header['kid'] ) ) { return new WP_Error( 'invalid_id_token' ); }
        $keys = get_transient( 'yatoon_apple_jwks' );
        if ( ! is_array( $keys ) ) {
            $response = wp_remote_get( 'https://appleid.apple.com/auth/keys', array( 'timeout' => 15 ) );
            $keys = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ? json_decode( wp_remote_retrieve_body( $response ), true ) : null;
            if ( is_array( $keys ) ) { set_transient( 'yatoon_apple_jwks', $keys, 12 * HOUR_IN_SECONDS ); }
        }
        $jwk = null;
        foreach ( (array) ( $keys['keys'] ?? array() ) as $candidate ) { if ( hash_equals( (string) $header['kid'], (string) ( $candidate['kid'] ?? '' ) ) ) { $jwk = $candidate; break; } }
        $pem = is_array( $jwk ) ? self::rsa_jwk_to_pem( $jwk ) : '';
        $verified = $pem && 1 === openssl_verify( $parts[0] . '.' . $parts[1], self::base64url_decode( $parts[2] ), $pem, OPENSSL_ALGO_SHA256 );
        $aud = (array) ( $payload['aud'] ?? array() );
        $email = sanitize_email( $payload['email'] ?? '' );
        if ( ! $verified || 'https://appleid.apple.com' !== ( $payload['iss'] ?? '' ) || ! in_array( self::service_id(), $aud, true )
            || (int) ( $payload['exp'] ?? 0 ) < time() - 30 || (int) ( $payload['iat'] ?? 0 ) > time() + 300
            || ! hash_equals( (string) $nonce, (string) ( $payload['nonce'] ?? '' ) ) || empty( $payload['sub'] )
            || ( $email && ! filter_var( $payload['email_verified'] ?? false, FILTER_VALIDATE_BOOLEAN ) ) ) {
            return new WP_Error( 'invalid_id_claims' );
        }
        return array( 'sub' => sanitize_text_field( $payload['sub'] ), 'email' => $email, 'name' => '', 'is_private_email' => filter_var( $payload['is_private_email'] ?? false, FILTER_VALIDATE_BOOLEAN ) );
    }

    private static function remember_link( $account_id, array $claims, $square_id ) {
        global $wpdb;
        $table = $wpdb->prefix . 'yatoon_identity_links';
        $where = array( 'account_type' => 'customer', 'provider' => 'apple', 'provider_subject' => sanitize_text_field( $claims['sub'] ) );
        $id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table} WHERE account_type='customer' AND provider='apple' AND provider_subject=%s LIMIT 1", $claims['sub'] ) );
        $data = array( 'account_id' => absint( $account_id ), 'verified_email' => sanitize_email( $claims['email'] ?? '' ), 'external_account_id' => sanitize_text_field( $square_id ), 'last_login_at' => current_time( 'mysql', true ) );
        if ( $id ) { $wpdb->update( $table, $data, array( 'id' => absint( $id ) ), array( '%d', '%s', '%s', '%s' ), array( '%d' ) ); }
        else { $wpdb->insert( $table, array_merge( $where, $data, array( 'created_at' => current_time( 'mysql', true ) ) ), array( '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s' ) ); }
    }

    /** Attach an authenticated Apple identity to a booking created in-session. */
    public static function reconcile_customer_link( $booking ) {
        if ( ! class_exists( 'YATOON_Customer_Portal' ) || ! YATOON_Customer_Portal::get_instance() ) { return; }
        $customer = (array) YATOON_Customer_Portal::get_instance()->current_customer();
        if ( 'apple' !== ( $customer['identity_provider'] ?? '' ) || empty( $customer['identity_sub'] ) ) { return; }
        global $wpdb;
        $email = sanitize_email( $booking['customer_email'] ?? '' );
        $phone = preg_replace( '/\D+/', '', sanitize_text_field( $booking['customer_phone'] ?? '' ) );
        $client_id = 0;
        if ( $email ) {
            $client_id = absint( $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}yatoon_clients WHERE LOWER(customer_email)=LOWER(%s) ORDER BY id DESC LIMIT 1", $email ) ) );
        }
        if ( ! $client_id && $phone ) {
            $rows = $wpdb->get_results( "SELECT id,customer_phone FROM {$wpdb->prefix}yatoon_clients ORDER BY id DESC LIMIT 500" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery
            foreach ( $rows as $row ) { if ( $phone === preg_replace( '/\D+/', '', (string) $row->customer_phone ) ) { $client_id = absint( $row->id ); break; } }
        }
        $update = array( 'account_id' => $client_id, 'last_login_at' => current_time( 'mysql', true ) );
        $formats = array( '%d', '%s' );
        if ( ! empty( $booking['square_booking_id'] ) && class_exists( 'YATOON_Square_API' ) ) {
            $result = ( new YATOON_Square_API() )->get_booking( sanitize_text_field( $booking['square_booking_id'] ) );
            $square_id = is_array( $result ) ? sanitize_text_field( $result['booking']['customer_id'] ?? '' ) : '';
            if ( $square_id ) { $update['external_account_id'] = $square_id; $formats[] = '%s'; }
        }
        $wpdb->update( $wpdb->prefix . 'yatoon_identity_links', $update, array( 'account_type' => 'customer', 'provider' => 'apple', 'provider_subject' => sanitize_text_field( $customer['identity_sub'] ) ), $formats, array( '%s', '%s', '%s' ) );
    }

    private static function rsa_jwk_to_pem( array $jwk ) {
        if ( empty( $jwk['n'] ) || empty( $jwk['e'] ) ) { return ''; }
        $modulus = self::asn1_integer( self::base64url_decode( $jwk['n'] ) );
        $exponent = self::asn1_integer( self::base64url_decode( $jwk['e'] ) );
        $rsa = "\x30" . self::asn1_length( strlen( $modulus . $exponent ) ) . $modulus . $exponent;
        $algorithm = hex2bin( '300d06092a864886f70d0101010500' );
        $bitstring = "\x03" . self::asn1_length( strlen( $rsa ) + 1 ) . "\x00" . $rsa;
        $spki = "\x30" . self::asn1_length( strlen( $algorithm . $bitstring ) ) . $algorithm . $bitstring;
        return "-----BEGIN PUBLIC KEY-----\n" . chunk_split( base64_encode( $spki ), 64, "\n" ) . "-----END PUBLIC KEY-----\n";
    }

    private static function asn1_integer( $bytes ) {
        $bytes = ltrim( (string) $bytes, "\x00" );
        if ( '' === $bytes ) { $bytes = "\x00"; }
        if ( ord( $bytes[0] ) & 0x80 ) { $bytes = "\x00" . $bytes; }
        return "\x02" . self::asn1_length( strlen( $bytes ) ) . $bytes;
    }

    private static function asn1_length( $length ) {
        if ( $length < 128 ) { return chr( $length ); }
        $encoded = '';
        while ( $length > 0 ) { $encoded = chr( $length & 0xff ) . $encoded; $length >>= 8; }
        return chr( 0x80 | strlen( $encoded ) ) . $encoded;
    }

    private static function der_to_jose( $der ) {
        $offset = 0;
        if ( ord( $der[ $offset++ ] ?? "\0" ) !== 0x30 ) { return ''; }
        self::der_length( $der, $offset );
        if ( ord( $der[ $offset++ ] ?? "\0" ) !== 0x02 ) { return ''; }
        $rlen = self::der_length( $der, $offset ); $r = substr( $der, $offset, $rlen ); $offset += $rlen;
        if ( ord( $der[ $offset++ ] ?? "\0" ) !== 0x02 ) { return ''; }
        $slen = self::der_length( $der, $offset ); $s = substr( $der, $offset, $slen );
        $r = str_pad( substr( ltrim( $r, "\x00" ), -32 ), 32, "\x00", STR_PAD_LEFT );
        $s = str_pad( substr( ltrim( $s, "\x00" ), -32 ), 32, "\x00", STR_PAD_LEFT );
        return $r . $s;
    }

    private static function der_length( $data, &$offset ) {
        $length = ord( $data[ $offset++ ] ?? "\0" );
        if ( $length < 128 ) { return $length; }
        $bytes = $length & 0x7f; $length = 0;
        while ( $bytes-- > 0 ) { $length = ( $length << 8 ) | ord( $data[ $offset++ ] ?? "\0" ); }
        return $length;
    }

    private static function base64url_encode( $value ) { return rtrim( strtr( base64_encode( (string) $value ), '+/', '-_' ), '=' ); }
    private static function base64url_decode( $value ) { $value = strtr( (string) $value, '-_', '+/' ); return (string) base64_decode( $value . str_repeat( '=', ( 4 - strlen( $value ) % 4 ) % 4 ), true ); }
    private static function fingerprint() { return hash_hmac( 'sha256', substr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) ), 0, 255 ), wp_salt( 'nonce' ) ); }
    private static function safe_return_url( $url ) { $fallback = self::portal_fallback(); $url = wp_validate_redirect( esc_url_raw( rawurldecode( (string) $url ) ), $fallback ); return 0 === strpos( $url, home_url() ) ? $url : $fallback; }
    private static function portal_fallback() { return class_exists( 'YATOON_Customer_Portal' ) && YATOON_Customer_Portal::portal_url() ? YATOON_Customer_Portal::portal_url() : home_url( '/' ); }
    private static function redirect_error( $return_url, $code ) { wp_safe_redirect( add_query_arg( 'yatoon_identity_error', sanitize_key( $code ), self::safe_return_url( $return_url ) ) ); exit; }
}
