<?php
/**
 * DESCRIPTION: Single Event Shortcode — renders the [nemtly_event] widget as a
 * DESCRIPTION: server-rendered themed event hero plus a React booking-wizard
 * DESCRIPTION: mount point, with JSON-LD Event schema emitted for SEO.
 *
 * @package NemtlyBooking
 * @since   PBOOKING_SINCE
 */

namespace NemtlyBooking\Shortcodes;

use NemtlyBooking\Core\Template;
use NemtlyBooking\Models\Event;
use NemtlyBooking\Repositories\Event_Repository;
use NemtlyBooking\Services\Settings_Service;

/**
 * Single Event Shortcode class
 *
 * Renders the complete event widget — a server-side themed hero (title, meta,
 * description, price, featured image) followed by an inline booking wizard —
 * for one event.
 *
 * The hero is rendered as real HTML by PHP for instant paint and SEO indexing.
 * The interactive booking wizard is hydrated client-side into a dedicated
 * `.nb-booking-wizard` mount, seeded with the event data so it paints without
 * a loading flash or an extra network round-trip.
 *
 * @since PBOOKING_SINCE
 */
class Single_Event_Shortcode {

    /**
     * Event repository instance
     *
     * @var Event_Repository
     */
    private $event_repository;

    /**
     * Settings service instance
     *
     * @var Settings_Service
     */
    private $settings_service;

    /**
     * Constructor
     *
     * @since PBOOKING_SINCE
     *
     * @param Event_Repository $event_repository Event repository instance.
     * @param Settings_Service $settings_service  Settings service instance.
     */
    public function __construct( Event_Repository $event_repository, Settings_Service $settings_service ) {
        $this->event_repository = $event_repository;
        $this->settings_service = $settings_service;

        add_shortcode( 'nemtly_event', [ $this, 'render' ] );
    }

    /**
     * Render the [nemtly_event] shortcode.
     *
     * Usage: [nemtly_event id="123"]
     *
     * @since PBOOKING_SINCE
     *
     * @param array $atts Shortcode attributes.
     *
     * @return string HTML output.
     */
    public function render( $atts ) {
        $atts = shortcode_atts(
            [
                'id'    => '',
                'class' => '',
            ],
            $atts,
            'nemtly_event'
        );

        // Resolve the event ID, falling back to the current post in the loop.
        $event_id = ! empty( $atts['id'] ) ? absint( $atts['id'] ) : 0;
        if ( ! $event_id ) {
            $event_id = get_the_ID();
        }

        $event = $event_id ? $this->event_repository->find( $event_id ) : null;

        // Bail silently when the event does not exist or is not published.
        if ( ! $event || 'publish' !== $event->get_status() ) {
            return '';
        }

        $this->enqueue_assets();

        $css_class = sanitize_html_class( $atts['class'] );

        /**
         * Filter the wrapper CSS classes for the [nemtly_event] widget.
         *
         * @since PBOOKING_SINCE
         *
         * @param string[] $classes CSS classes applied to the widget wrapper.
         * @param Event    $event   The event being rendered.
         */
        $wrapper_classes = apply_filters(
            'nemtly_booking_single_event_classes',
            array_filter( [ 'nb-event-widget', $css_class ] ),
            $event
        );

        /**
         * Filter the event data seed passed from PHP to the booking wizard
         * React app. The seed is JSON-encoded into the wrapper's
         * `data-initial-event` attribute.
         *
         * @since PBOOKING_SINCE
         *
         * @param array $seed Event data seed.
         * @param Event $event The event being rendered.
         */
        $seed = apply_filters(
            'nemtly_booking_single_event_seed',
            $this->build_seed( $event ),
            $event
        );

        /**
         * Filter the JSON-LD Event schema array. Return an empty array to
         * suppress schema output entirely.
         *
         * @since PBOOKING_SINCE
         *
         * @param array $schema JSON-LD schema array.
         * @param Event $event  The event being rendered.
         */
        $json_ld = apply_filters(
            'nemtly_booking_event_json_ld',
            $this->build_json_ld( $event ),
            $event
        );

        $output = Template::render(
            'shortcodes/single-event.php',
            [
                'event'           => $event,
                'seed'            => $seed,
                'style_vars'      => $this->build_style_vars( $event ),
                'wrapper_classes' => $wrapper_classes,
                'json_ld'         => $json_ld,
                'atts'            => $atts,
            ]
        );

        /**
         * Filter the final [nemtly_event] shortcode HTML output.
         *
         * @since PBOOKING_SINCE
         *
         * @param string $output Rendered shortcode HTML.
         * @param Event  $event  The event being rendered.
         */
        return apply_filters( 'nemtly_booking_single_event_html', $output, $event );
    }

    /**
     * Build the event data seed consumed by the React booking wizard.
     *
     * The seed intentionally carries both the canonical REST keys and the
     * aliases the React components consume directly (`date`, `time`,
     * `duration`, `description`). This keeps the wizard self-sufficient on
     * first paint — no loading flash, no extra GET — regardless of which
     * shape the components read.
     *
     * @since PBOOKING_SINCE
     *
     * @param Event $event The event being rendered.
     *
     * @return array Event seed data.
     */
    private function build_seed( Event $event ): array {
        $currency = $this->settings_service->get_setting( 'currency', 'USD' );

		return [
			'id'              => $event->get_id(),
			'title'           => $event->get_title(),
			'description'     => $event->get_description(),
			'date'            => $event->get_formatted_date(),
			'time'            => $event->get_formatted_time(),
			'event_date'      => $event->get_date(),
			'event_time'      => $event->get_time(),
			'end_date'        => $event->get_end_date(),
			'end_time'        => $event->get_end_time(),
			'duration'        => $event->get_duration(),
			'event_type'      => $event->get_event_type() ? $event->get_event_type() : 'one-on-one',
			'price'           => (float) $event->get_price(),
			'currency'        => $currency,
			'currency_symbol' => get_option( 'nemtly_currency_symbol', '$' ),
			'location'        => $event->get_location(),
			'timezone'        => $event->get_timezone() ? $event->get_timezone() : wp_timezone_string(),
			'featured_image'  => get_the_post_thumbnail_url( $event->get_id(), 'medium' ) ? get_the_post_thumbnail_url( $event->get_id(), 'medium' ) : '',
			'styling'         => $event->get_styling(),
			'allow_waitlist'  => (bool) $event->get_allow_waitlist(),
			'availability'    => $event->get_availability(),
		];
    }

    /**
     * Build inline CSS custom properties from an event's per-event styling
     * tokens.
     *
     * PHP port of the JS `getEventStyleVars()` helper so the server-rendered
     * hero honours the same per-event overrides (accent, surface, radius,
     * shadow, spacing, typography) as the React wizard. Empty tokens are
     * omitted so the defaults declared in `event-hero.css` keep applying.
     *
     * @since PBOOKING_SINCE
     *
     * @param Event $event The event being rendered.
     *
     * @return string Inline `style` attribute value (without the wrapper quotes).
     */
    private function build_style_vars( Event $event ): string {
        $styling = $event->get_styling();
        if ( ! is_array( $styling ) ) {
            $styling = [];
        }

        $vars = [];

        // Color tokens — each maps to one visible element.
        $color_map = [
            'header_background' => '--nb-accent',
            'header_title_text' => '--nb-title-text',
            'tag_text'          => '--nb-pill-text',
            'tag_background'    => '--nb-pill-bg',
            'detail_text'       => '--nb-text-secondary',
            'price_text'        => '--nb-price-text',
            'button_background' => '--nb-button-bg',
            'button_text'       => '--nb-button-text',
            'card_background'   => '--nb-surface',
            'border_color'      => '--nb-border-subtle',
        ];

        foreach ( $color_map as $token => $var ) {
            if ( ! empty( $styling[ $token ] ) ) {
                $vars[ $var ] = $styling[ $token ];
            }
        }

        // Shape tokens.
        $radius_values = [
            'sharp'   => '0',
            'rounded' => '0.5rem',
            'pill'    => '9999px',
        ];

        if ( ! empty( $styling['card_radius'] ) && isset( $radius_values[ $styling['card_radius'] ] ) ) {
            $vars['--nb-radius'] = $radius_values[ $styling['card_radius'] ];
        }
        if ( ! empty( $styling['button_radius'] ) && isset( $radius_values[ $styling['button_radius'] ] ) ) {
            $vars['--nb-button-radius'] = $radius_values[ $styling['button_radius'] ];
        }

        $shadow_values = [
            'none'   => 'none',
            'low'    => '0 1px 2px rgba(0, 0, 0, 0.04), 0 0 0 1px rgba(0, 0, 0, 0.04)',
            'medium' => '0 4px 12px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.06)',
            'high'   => '0 8px 32px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.08)',
        ];

        if ( ! empty( $styling['shadow'] ) && isset( $shadow_values[ $styling['shadow'] ] ) ) {
            $vars['--nb-shadow'] = $shadow_values[ $styling['shadow'] ];
        }

        // Spacing density multiplier.
        $spacing_values = [
            'compact' => '0.8',
            'normal'  => '1',
            'loose'   => '1.25',
        ];

        if ( ! empty( $styling['spacing'] ) && isset( $spacing_values[ $styling['spacing'] ] ) ) {
            $vars['--nb-spacing-mult'] = $spacing_values[ $styling['spacing'] ];
        }

        // Typography tokens.
        $heading_size_values = [
            'sm' => '1.5rem',
            'md' => '2rem',
            'lg' => '2.5rem',
        ];

        if ( ! empty( $styling['heading_size'] ) && isset( $heading_size_values[ $styling['heading_size'] ] ) ) {
            $vars['--nb-heading-size'] = $heading_size_values[ $styling['heading_size'] ];
        }

        $heading_weight_values = [
            'normal'   => '400',
            'medium'   => '500',
            'semibold' => '600',
            'bold'     => '700',
        ];

        if ( ! empty( $styling['heading_weight'] ) && isset( $heading_weight_values[ $styling['heading_weight'] ] ) ) {
            $vars['--nb-heading-weight'] = $heading_weight_values[ $styling['heading_weight'] ];
        }

        $out = '';
        foreach ( $vars as $name => $value ) {
            $out .= $name . ': ' . $value . '; ';
        }

        return trim( $out );
    }

    /**
     * Build the JSON-LD Event schema for an event.
     *
     * Uses the event's concrete date/time. For slot-based or recurring events
     * this reflects the event's base occurrence; filter
     * `nemtly_booking_event_json_ld` to substitute a computed next occurrence.
     *
     * @since PBOOKING_SINCE
     *
     * @param Event $event The event being rendered.
     *
     * @return array JSON-LD schema array.
     */
    private function build_json_ld( Event $event ): array {
        $tz         = $event->get_timezone() ? $event->get_timezone() : wp_timezone_string();
        $start_date = $event->get_date();
        $start_time = $event->get_time();
        $end_date   = $event->get_end_date() ? $event->get_end_date() : $start_date;
        $end_time   = $event->get_end_time();
        $duration   = (int) $event->get_duration();

        $schema = [
            '@context'    => 'https://schema.org',
            '@type'       => 'Event',
            'name'        => $event->get_title(),
            'description' => wp_strip_all_tags( $event->get_description() ),
            'eventStatus' => 'https://schema.org/EventScheduled',
        ];

        if ( $start_date ) {
            $schema['startDate'] = $this->build_iso8601( $start_date, $start_time, $tz );

            if ( $end_time ) {
                $schema['endDate'] = $this->build_iso8601( $end_date, $end_time, $tz );
            } elseif ( $duration > 0 && $start_time ) {
                $schema['endDate'] = $this->build_iso8601( $start_date, $start_time, $tz, $duration * 60 );
            } else {
                $schema['endDate'] = $schema['startDate'];
            }
        }

        $location = $event->get_location();
        if ( $location ) {
            if ( filter_var( $location, FILTER_VALIDATE_URL ) ) {
                $schema['location'] = [
                    '@type' => 'VirtualLocation',
                    'url'   => $location,
                ];
            } else {
                $schema['location'] = [
                    '@type'   => 'Place',
                    'name'    => $location,
                    'address' => $location,
                ];
            }
        }

        $image = get_the_post_thumbnail_url( $event->get_id(), 'large' );
        if ( $image ) {
            $schema['image'] = $image;
        }

        $price = (float) $event->get_price();
        if ( $price > 0 ) {
            $schema['offers'] = [
                '@type'         => 'Offer',
                'price'         => $price,
                'priceCurrency' => $this->settings_service->get_setting( 'currency', 'USD' ),
                'availability'  => 'https://schema.org/InStock',
                'url'           => get_permalink( $event->get_id() ),
            ];
        } else {
            $schema['isAccessibleForFree'] = true;
        }

        return $schema;
    }

    /**
     * Compose an ISO-8601 timestamp from a date, optional time, timezone, and
     * optional offset in seconds.
     *
     * @since PBOOKING_SINCE
     *
     * @param string $date       Y-m-d date.
     * @param string $time       H:i time, or empty.
     * @param string $timezone   Timezone identifier.
     * @param int    $offset_sec Optional seconds to add (for derived end times).
     *
     * @return string ISO-8601 timestamp, or the date alone on failure.
     */
    private function build_iso8601( $date, $time, $timezone, $offset_sec = 0 ) {
        $time = $time ? $time : '00:00';

        try {
            $zone = new \DateTimeZone( $timezone );
            $dt   = new \DateTime( $date . ' ' . $time, $zone );

            if ( $offset_sec > 0 ) {
                $dt->modify( '+' . $offset_sec . ' seconds' );
            }

            return $dt->format( 'c' );
        } catch ( \Exception $e ) {
            return $date;
        }
    }

    /**
     * Enqueue the booking-form script and styles.
     *
     * @since PBOOKING_SINCE
     */
    private function enqueue_assets() {
        if ( wp_script_is( 'nemtly-booking-form', 'enqueued' ) ) {
            return;
        }

        wp_enqueue_style(
            'nemtly-booking-form-styles',
            plugin_dir_url( dirname( __DIR__ ) ) . 'assets/css/booking-form.css',
            [],
            NEMTLY_BOOKING_VERSION
        );

        $asset_path = plugin_dir_path( dirname( __DIR__ ) ) . 'assets/js/booking-form.asset.php';
        $asset      = file_exists( $asset_path ) ? require $asset_path : [
            'dependencies' => [ 'wp-element', 'wp-api-fetch', 'wp-i18n' ],
            'version'      => NEMTLY_BOOKING_VERSION,
        ];

        wp_enqueue_script(
            'nemtly-booking-form',
            plugin_dir_url( dirname( __DIR__ ) ) . 'assets/js/booking-form.js',
            $asset['dependencies'],
            $asset['version'],
            true
        );

        $settings_service = new \NemtlyBooking\Services\Settings_Service();
        $plugin_settings  = $settings_service->get_settings();

        wp_localize_script(
            'nemtly-booking-form',
            'nemtlyBookingAjax',
            [
                'nonce'     => wp_create_nonce( 'nemtly_nonce' ),
                'ajaxUrl'   => admin_url( 'admin-ajax.php' ),
                'restUrl'   => rest_url( 'nemtly-booking/v1/' ),
                'restNonce' => wp_create_nonce( 'wp_rest' ),
                'legal'     => [
                    'terms_url'   => esc_url( (string) apply_filters( 'nemtly_booking_terms_url', '' ) ),
                    'privacy_url' => esc_url( (string) apply_filters( 'nemtly_booking_privacy_url', (string) get_privacy_policy_url() ) ),
                ],
                'strings'   => [
                    'loading'        => __( 'Loading...', 'nemtly-booking' ),
                    'processing'     => __( 'Processing...', 'nemtly-booking' ),
                    'error'          => __( 'An error occurred. Please try again.', 'nemtly-booking' ),
                    'success'        => __( 'Booking successful!', 'nemtly-booking' ),
                    'confirmBooking' => __( 'Confirm Booking', 'nemtly-booking' ),
                    'cancel'         => __( 'Cancel', 'nemtly-booking' ),
                    'requiredField'  => __( 'This field is required.', 'nemtly-booking' ),
                    'invalidEmail'   => __( 'Please enter a valid email address.', 'nemtly-booking' ),
                ],
                'captcha'   => [
                    'type'          => $plugin_settings['captcha_type'] ?? 'disabled',
                    'site_key'      => 'turnstile' === ( $plugin_settings['captcha_type'] ?? '' )
                        ? esc_attr( $plugin_settings['turnstile_site_key'] ?? '' )
                        : '',
                    'site_key_name' => 'turnstile',
                ],
            ]
        );

        wp_add_inline_script(
            'nemtly-booking-form',
            sprintf(
                'wp.apiFetch.use( wp.apiFetch.createNonceMiddleware( %s ) );',
                wp_json_encode( wp_create_nonce( 'wp_rest' ) )
            ),
            'before'
        );

        wp_set_script_translations( 'nemtly-booking-form', 'nemtly-booking' );
    }
}
