<?php
/**
 * Native Gutenberg blocks and Elementor widgets for the plugin's shortcodes.
 *
 * Each "surface" below is a thin wrapper around an existing shortcode --
 * there is exactly one source of truth for what gets rendered (do_shortcode),
 * so this file only has to describe the editable fields for each one.
 */
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class YATOON_Editor_Integrations {
    private static $elementor_registered = false;

    /**
     * One entry per shortcode we expose as a block/widget.
     * 'atts' describes the editable fields: type is one of
     * 'text' | 'number' | 'select' | 'boolean'.
     */
    private static function surfaces() {
        $surfaces = array(
            'booking' => array(
                'title'       => __( 'Yatoon Booking Form', 'yatoon-booking-system' ),
                'description' => __( 'The full multi-step appointment booking flow.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_booking',
                'dashicon'    => 'calendar-alt',
                'eicon'       => 'eicon-calendar',
                'atts'        => array(
                    'location_id' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Location ID (leave blank for single-location businesses)', 'yatoon-booking-system' ) ),
                    'service_id'  => array( 'type' => 'text', 'default' => '', 'label' => __( 'Preselected service', 'yatoon-booking-system' ) ),
                    'staff_id'    => array( 'type' => 'text', 'default' => '', 'label' => __( 'Preferred staff', 'yatoon-booking-system' ) ),
                    'category'    => array( 'type' => 'text', 'default' => '', 'label' => __( 'Starting category', 'yatoon-booking-system' ) ),
                    'mode'        => array( 'type' => 'select', 'default' => 'standard', 'label' => __( 'Page mode', 'yatoon-booking-system' ), 'options' => array( 'standard' => __( 'Standard', 'yatoon-booking-system' ), 'landing' => __( 'Landing page', 'yatoon-booking-system' ) ) ),
                ),
            ),
            'service-menu' => array(
                'title'       => __( 'Yatoon Service Menu', 'yatoon-booking-system' ),
                'description' => __( 'A visual, categorized menu of services with Book buttons.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_service_menu',
                'dashicon'    => 'index-card',
                'eicon'       => 'eicon-bullet-list',
                'atts'        => array(
                    'title' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Heading (optional)', 'yatoon-booking-system' ) ),
                    'category' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Category filter (optional)', 'yatoon-booking-system' ) ),
                ),
            ),
            'gallery' => array(
                'title'       => __( 'Yatoon Gallery', 'yatoon-booking-system' ),
                'description' => __( 'A photo gallery of your work, optionally filtered by service or category.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_gallery',
                'dashicon'    => 'format-gallery',
                'eicon'       => 'eicon-gallery-grid',
                'atts'        => array(
                    'service_id' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Service filter (optional)', 'yatoon-booking-system' ) ),
                    'category' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Category filter (optional)', 'yatoon-booking-system' ) ),
                    'limit'    => array( 'type' => 'number', 'default' => 50, 'label' => __( 'Max photos', 'yatoon-booking-system' ) ),
                    'cols'     => array( 'type' => 'number', 'default' => 5, 'label' => __( 'Columns', 'yatoon-booking-system' ) ),
                    'mode'     => array( 'type' => 'select', 'default' => 'grid', 'label' => __( 'Layout', 'yatoon-booking-system' ), 'options' => array( 'grid' => __( 'Grid', 'yatoon-booking-system' ), 'carousel' => __( 'Carousel', 'yatoon-booking-system' ) ) ),
                    'lightbox' => array( 'type' => 'boolean', 'default' => true, 'label' => __( 'Enable lightbox on click', 'yatoon-booking-system' ) ),
                    'autoplay' => array( 'type' => 'boolean', 'default' => true, 'label' => __( 'Autoplay (carousel mode)', 'yatoon-booking-system' ) ),
                ),
            ),
            'catalog' => array(
                'title'       => __( 'Yatoon Catalog', 'yatoon-booking-system' ),
                'description' => __( 'A browsable catalog of services with photos and pricing.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_catalog',
                'dashicon'    => 'grid-view',
                'eicon'       => 'eicon-posts-grid',
                'atts'        => array(
                    'category' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Category filter (optional)', 'yatoon-booking-system' ) ),
                    'limit'    => array( 'type' => 'number', 'default' => 50, 'label' => __( 'Max items', 'yatoon-booking-system' ) ),
                    'cols'     => array( 'type' => 'number', 'default' => 3, 'label' => __( 'Columns', 'yatoon-booking-system' ) ),
                ),
            ),
            'reviews' => array(
                'title'       => __( 'Yatoon Reviews', 'yatoon-booking-system' ),
                'description' => __( 'Customer reviews collected through Yatoon. Requires Yatoon Pro.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_reviews',
                'dashicon'    => 'star-filled',
                'eicon'       => 'eicon-testimonial',
                'atts'        => array(
                    'limit'      => array( 'type' => 'number', 'default' => 10, 'label' => __( 'Max reviews', 'yatoon-booking-system' ) ),
                    'min_rating' => array( 'type' => 'number', 'default' => 4, 'label' => __( 'Minimum star rating', 'yatoon-booking-system' ) ),
                ),
            ),
            'staff-portal' => array(
                'title'       => __( 'Yatoon Staff Portal', 'yatoon-booking-system' ),
                'description' => __( 'The staff sign-in and schedule portal. Intended for one dedicated page.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_staff_portal',
                'dashicon'    => 'groups',
                'eicon'       => 'eicon-lock-user',
                'atts'        => array(),
            ),
            'customer-portal' => array(
                'title'       => __( 'Yatoon Customer Portal', 'yatoon-booking-system' ),
                'description' => __( 'Lets customers look up and manage their own appointments. Intended for one dedicated page.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_customer_portal',
                'dashicon'    => 'id',
                'eicon'       => 'eicon-lock-user',
                'atts'        => array(
                    'class' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Extra CSS class (optional)', 'yatoon-booking-system' ) ),
                ),
            ),
            'growth-storefront' => array(
                'title'       => __( 'Yatoon Package Storefront', 'yatoon-booking-system' ),
                'description' => __( 'Displays packages and memberships customers can purchase.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_growth_storefront',
                'dashicon'    => 'store',
                'eicon'       => 'eicon-products',
                'atts'        => array(
                    'show'          => array( 'type' => 'select', 'default' => 'both', 'label' => __( 'Products to show', 'yatoon-booking-system' ), 'options' => array( 'both' => __( 'Packages and memberships', 'yatoon-booking-system' ), 'packages' => __( 'Packages only', 'yatoon-booking-system' ), 'memberships' => __( 'Memberships only', 'yatoon-booking-system' ) ) ),
                    'package_id'    => array( 'type' => 'text', 'default' => '', 'label' => __( 'Specific package (optional)', 'yatoon-booking-system' ) ),
                    'membership_id' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Specific membership (optional)', 'yatoon-booking-system' ) ),
                    'title'         => array( 'type' => 'text', 'default' => '', 'label' => __( 'Heading (optional)', 'yatoon-booking-system' ) ),
                ),
            ),
            'manage-booking' => array(
                'title'       => __( 'Yatoon Manage Booking', 'yatoon-booking-system' ),
                'description' => __( 'Customer self-service page opened from secure booking links. Intended for one dedicated page.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_manage_booking',
                'dashicon'    => 'edit-page',
                'eicon'       => 'eicon-edit',
                'atts'        => array(),
            ),
            'booking-popup' => array(
                'title'       => __( 'Yatoon Booking Popup', 'yatoon-booking-system' ),
                'description' => __( 'A call-to-action button that opens the booking form in an accessible dialog.', 'yatoon-booking-system' ),
                'shortcode'   => 'yatoon_booking_popup',
                'dashicon'    => 'welcome-view-site',
                'eicon'       => 'eicon-call-to-action',
                'atts'        => array(
                    'button_label' => array( 'type' => 'text', 'default' => '', 'label' => __( 'Button label', 'yatoon-booking-system' ) ),
                    'location_id'  => array( 'type' => 'text', 'default' => '', 'label' => __( 'Location', 'yatoon-booking-system' ) ),
                    'service_id'   => array( 'type' => 'text', 'default' => '', 'label' => __( 'Preselected service', 'yatoon-booking-system' ) ),
                    'staff_id'     => array( 'type' => 'text', 'default' => '', 'label' => __( 'Preferred staff', 'yatoon-booking-system' ) ),
                ),
            ),
        );
        $has_pro_features = ( ! function_exists( 'yatoon_is_pro' ) || yatoon_is_pro() )
            && is_dir( YATOON_PLUGIN_DIR . 'includes/pro' );
        if ( ! $has_pro_features ) {
            unset( $surfaces['reviews'], $surfaces['growth-storefront'] );
        }
        return $surfaces;
    }

    /** Replace hand-entered identifiers with choices when an editor is open. */
    private static function editor_surface( $slug, $surface ) {
        if ( ! is_admin() ) {
            return $surface;
        }
        global $wpdb;
        if ( $slug === 'booking' ) {
            $options = array( '' => __( 'All locations / default location', 'yatoon-booking-system' ) );
            $locations = yatoon_db_get_results( "SELECT id, name FROM {$wpdb->prefix}yatoon_locations WHERE is_active=1 ORDER BY name ASC" );
            foreach ( (array) $locations as $location ) {
                $options[ (string) absint( $location->id ) ] = (string) $location->name;
            }
            $surface['atts']['location_id']['type']    = 'select';
            $surface['atts']['location_id']['label']   = __( 'Location', 'yatoon-booking-system' );
            $surface['atts']['location_id']['options'] = $options;
        }
        if ( in_array( $slug, array( 'booking', 'booking-popup', 'gallery' ), true ) ) {
            $service_options = array( '' => __( 'Any service', 'yatoon-booking-system' ) );
            $services = yatoon_db_get_results( "SELECT id, name FROM {$wpdb->prefix}yatoon_services WHERE is_active=1 ORDER BY name ASC" );
            foreach ( (array) $services as $service ) {
                $service_options[ (string) absint( $service->id ) ] = (string) $service->name;
            }
            $surface['atts']['service_id']['type']    = 'select';
            $surface['atts']['service_id']['options'] = $service_options;
        }
        if ( in_array( $slug, array( 'booking', 'booking-popup' ), true ) ) {
            $staff_options = array( '' => __( 'Any available staff', 'yatoon-booking-system' ) );
            $staff = yatoon_db_get_results( "SELECT id, name FROM {$wpdb->prefix}yatoon_staff WHERE is_active=1 AND is_bookable=1 ORDER BY name ASC" );
            foreach ( (array) $staff as $person ) {
                $staff_options[ (string) absint( $person->id ) ] = (string) $person->name;
            }
            $surface['atts']['staff_id']['type']    = 'select';
            $surface['atts']['staff_id']['options'] = $staff_options;
        }
        if ( in_array( $slug, array( 'booking', 'gallery', 'catalog', 'service-menu' ), true ) ) {
            $options = array( '' => __( 'All categories', 'yatoon-booking-system' ) );
            $categories = yatoon_db_get_col( "SELECT DISTINCT category FROM {$wpdb->prefix}yatoon_services WHERE category IS NOT NULL AND category != '' ORDER BY category ASC" );
            foreach ( (array) $categories as $category ) {
                $options[ (string) $category ] = (string) $category;
            }
            $surface['atts']['category']['type']    = 'select';
            $surface['atts']['category']['options'] = $options;
        }
        if ( $slug === 'growth-storefront' ) {
            $package_options = array( '' => __( 'All packages', 'yatoon-booking-system' ) );
            foreach ( (array) yatoon_db_get_results( "SELECT id, name FROM {$wpdb->prefix}yatoon_packages WHERE is_active=1 ORDER BY name ASC" ) as $package ) {
                $package_options[ (string) absint( $package->id ) ] = (string) $package->name;
            }
            $membership_options = array( '' => __( 'All memberships', 'yatoon-booking-system' ) );
            foreach ( (array) yatoon_db_get_results( "SELECT id, name FROM {$wpdb->prefix}yatoon_memberships WHERE is_active=1 ORDER BY name ASC" ) as $membership ) {
                $membership_options[ (string) absint( $membership->id ) ] = (string) $membership->name;
            }
            $surface['atts']['package_id']['type']          = 'select';
            $surface['atts']['package_id']['options']       = $package_options;
            $surface['atts']['membership_id']['type']       = 'select';
            $surface['atts']['membership_id']['options']    = $membership_options;
        }
        return $surface;
    }

    public static function init() {
        add_action( 'init', array( __CLASS__, 'register_blocks' ) );
        add_action( 'init', array( __CLASS__, 'register_patterns' ) );
        add_shortcode( 'yatoon_booking_popup', array( __CLASS__, 'render_popup_shortcode' ) );
        add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_editor_assets' ) );
        add_action( 'enqueue_block_assets', array( __CLASS__, 'enqueue_editor_preview_assets' ) );
        add_filter( 'block_categories_all', array( __CLASS__, 'register_block_category' ), 10, 2 );
        add_action( 'elementor/widgets/register', array( __CLASS__, 'register_elementor_widgets' ) );
        add_action( 'elementor/widgets/widgets_registered', array( __CLASS__, 'register_elementor_widgets_legacy' ) );
        add_action( 'elementor/elements/categories_registered', array( __CLASS__, 'register_elementor_category' ) );
    }

    public static function register_block_category( $categories, $editor_context ) {
        array_unshift( $categories, array( 'slug' => 'yatoon', 'title' => __( 'Yatoon Booking', 'yatoon-booking-system' ) ) );
        return $categories;
    }

    public static function register_elementor_category( $elements_manager ) {
        $elements_manager->add_category( 'yatoon', array( 'title' => __( 'Yatoon Booking', 'yatoon-booking-system' ), 'icon' => 'fa fa-calendar' ) );
    }

    /** Load complete preview assets only inside the block editor iframe. */
    public static function enqueue_editor_preview_assets() {
        if ( is_admin() ) {
            self::enqueue_shared_content_styles();
        }
    }

    /**
     * enqueue_block_editor_assets only affects the editor UI, not the
     * content inside the editor's iframe -- which is why ServerSideRender
     * previews of our blocks showed real data but no styling. Since
     * WordPress 6.3, enqueue_block_assets is the hook that can load styles
     * into the editor's content iframe. The wrapper above deliberately calls
     * this only in admin so unrelated front-end pages stay asset-free.
     */
    public static function enqueue_shared_content_styles() {
        if ( class_exists( 'YATOON_Frontend' ) && YATOON_Frontend::get_instance() ) {
            YATOON_Frontend::get_instance()->render_frontend_assets();
        }
        if ( class_exists( 'YATOON_Customer_Portal' ) && YATOON_Customer_Portal::get_instance() ) {
            YATOON_Customer_Portal::get_instance()->render_portal_css();
        }
        if ( class_exists( 'YATOON_Staff_Portal' ) ) {
            YATOON_Staff_Portal::render_portal_assets();
        }
    }

    /** Build the shortcode string for a surface from a set of attribute values. */
    private static function build_shortcode( $shortcode, $schema, $values ) {
        $parts = array( $shortcode );
        foreach ( $schema as $key => $def ) {
            $value = isset( $values[ $key ] ) ? $values[ $key ] : $def['default'];
            if ( $value === '' || $value === null ) {
                continue;
            }
            if ( $def['type'] === 'boolean' ) {
                $value = $value ? '1' : '0';
            }
            $parts[] = $key . '="' . esc_attr( $value ) . '"';
        }
        return '[' . implode( ' ', $parts ) . ']';
    }

    /** -- Gutenberg -------------------------------------------------- */

    public static function register_blocks() {
        if ( ! function_exists( 'register_block_type' ) ) {
            return;
        }
        foreach ( self::surfaces() as $slug => $surface ) {
            $attributes = array();
            foreach ( $surface['atts'] as $key => $def ) {
                $gb_type = $def['type'] === 'number' ? 'number' : ( $def['type'] === 'boolean' ? 'boolean' : 'string' );
                $attributes[ $key ] = array(
                    'type'    => $gb_type,
                    'default' => $def['default'],
                );
            }
            $metadata_dir = YATOON_PLUGIN_DIR . 'blocks/' . $slug;
            register_block_type(
                file_exists( $metadata_dir . '/block.json' ) ? $metadata_dir : 'yatoon/' . $slug,
                array(
                    'api_version'     => 2,
                    'title'           => $surface['title'],
                    'description'     => $surface['description'],
                    'category'        => 'yatoon',
                    'icon'            => $surface['dashicon'],
                    'keywords'        => array( 'yatoon', 'booking', 'salon', 'appointment' ),
                    'attributes'      => $attributes,
                    'supports'        => array( 'html' => false, 'align' => array( 'wide', 'full' ) ),
                    'render_callback' => function ( $block_atts ) use ( $surface ) {
                        $shortcode = self::build_shortcode( $surface['shortcode'], $surface['atts'], $block_atts );
                        return do_shortcode( $shortcode );
                    },
                )
            );
        }
    }

    /** Register ready-made page layouts that create the correct blocks in one click. */
    public static function register_patterns() {
        if ( ! function_exists( 'register_block_pattern' ) ) {
            return;
        }
        if ( function_exists( 'register_block_pattern_category' ) ) {
            register_block_pattern_category( 'yatoon', array( 'label' => __( 'Yatoon Booking', 'yatoon-booking-system' ) ) );
        }
        register_block_pattern( 'yatoon/booking-landing', array(
            'title'       => __( 'Salon booking landing page', 'yatoon-booking-system' ),
            'description' => __( 'A focused heading, reassurance copy, and full-width booking form.', 'yatoon-booking-system' ),
            'categories'  => array( 'yatoon' ),
            'content'     => '<!-- wp:group {"layout":{"type":"constrained"}} --><div class="wp-block-group"><!-- wp:heading {"textAlign":"center","level":1} --><h1 class="wp-block-heading has-text-align-center">' . esc_html__( 'Book your next appointment', 'yatoon-booking-system' ) . '</h1><!-- /wp:heading --><!-- wp:paragraph {"align":"center"} --><p class="has-text-align-center">' . esc_html__( 'Choose your service, preferred professional, and a time that works for you.', 'yatoon-booking-system' ) . '</p><!-- /wp:paragraph --><!-- wp:yatoon/booking {"mode":"landing","align":"wide"} /--></div><!-- /wp:group -->',
        ) );
        register_block_pattern( 'yatoon/manage-booking-page', array(
            'title'      => __( 'Customer manage-booking page', 'yatoon-booking-system' ),
            'categories' => array( 'yatoon' ),
            'content'    => '<!-- wp:heading {"textAlign":"center"} --><h2 class="wp-block-heading has-text-align-center">' . esc_html__( 'Manage your appointment', 'yatoon-booking-system' ) . '</h2><!-- /wp:heading --><!-- wp:yatoon/manage-booking /-->',
        ) );
        register_block_pattern( 'yatoon/booking-popup-cta', array(
            'title'      => __( 'Booking popup call to action', 'yatoon-booking-system' ),
            'categories' => array( 'yatoon' ),
            'content'    => '<!-- wp:yatoon/booking-popup {"button_label":"' . esc_attr__( 'Book an appointment', 'yatoon-booking-system' ) . '"} /-->',
        ) );
    }

    /** Accessible no-OAuth booking dialog for landing pages and promotional sections. */
    public static function render_popup_shortcode( $atts = array() ) {
        $atts = shortcode_atts( array( 'button_label' => '', 'location_id' => '', 'service_id' => '', 'staff_id' => '' ), $atts, 'yatoon_booking_popup' );
        $label = $atts['button_label'] !== '' ? $atts['button_label'] : __( 'Book an appointment', 'yatoon-booking-system' );
        $id = wp_unique_id( 'yatoon-booking-dialog-' );
        $booking = self::build_shortcode( 'yatoon_booking', array(
            'location_id' => array( 'type' => 'text', 'default' => '' ),
            'service_id'  => array( 'type' => 'text', 'default' => '' ),
            'staff_id'    => array( 'type' => 'text', 'default' => '' ),
            'mode'        => array( 'type' => 'text', 'default' => 'standard' ),
        ), array_merge( $atts, array( 'mode' => 'landing' ) ) );
        ob_start();
        ?><div class="yatoon-booking-popup"><button type="button" class="button yatoon-booking-popup-trigger" data-dialog="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $label ); ?></button><dialog id="<?php echo esc_attr( $id ); ?>" class="yatoon-booking-dialog"><button type="button" class="yatoon-booking-dialog-close" aria-label="<?php esc_attr_e( 'Close booking dialog', 'yatoon-booking-system' ); ?>">&times;</button><?php echo do_shortcode( $booking ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Trusted shortcode output. ?></dialog></div><script>(function(){var d=document.getElementById(<?php echo wp_json_encode( $id ); ?>);if(!d)return;var b=document.querySelector('[data-dialog='+<?php echo wp_json_encode( $id ); ?>+']');var c=d.querySelector('.yatoon-booking-dialog-close');if(b)b.addEventListener('click',function(){d.showModal?d.showModal():d.setAttribute('open','open');});if(c)c.addEventListener('click',function(){d.close?d.close():d.removeAttribute('open');});d.addEventListener('click',function(e){if(e.target===d&&d.close)d.close();});})();</script><?php
        return ob_get_clean();
    }

    /**
     * The block above is registered server-side so saved content parses and
     * renders correctly, but without this script the block editor's
     * inserter has no idea the block type exists, so it never shows up in
     * "Add block" at all. This script is what actually makes the blocks
     * appear and be usable in the editor.
     */
    public static function enqueue_block_editor_assets() {
        // On a non-iframed block editor, ServerSideRender fetches each
        // preview via a separate REST API call. That request has no <head>
        // to print styles into, so anything only enqueued there (e.g. via
        // enqueue_block_assets) never actually reaches the page. Loading it
        // here too, on the editor page's own initial load, is what actually
        // makes it show up for that case.
        self::enqueue_shared_content_styles();

        $asset_file = YATOON_PLUGIN_DIR . 'assets/js/yatoon-blocks-editor' . YATOON_ASSET_SUFFIX . '.js';
        if ( ! file_exists( $asset_file ) ) {
            return;
        }
        wp_enqueue_script(
            'yatoon-blocks-editor',
            YATOON_PLUGIN_URL . 'assets/js/yatoon-blocks-editor' . YATOON_ASSET_SUFFIX . '.js',
            array( 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-server-side-render' ),
            YATOON_VERSION,
            true
        );

        $surfaces_for_js = array();
        foreach ( self::surfaces() as $slug => $surface ) {
            $surface = self::editor_surface( $slug, $surface );
            $surfaces_for_js[ $slug ] = array(
                'title'       => $surface['title'],
                'description' => $surface['description'],
                'icon'        => $surface['dashicon'],
                'atts'        => $surface['atts'],
            );
        }
        wp_localize_script( 'yatoon-blocks-editor', 'YatoonBlocksData', $surfaces_for_js );
    }

    /** -- Elementor ---------------------------------------------------
     * One distinct, named widget per surface (rather than a single widget
     * with a dropdown) so each shows up on its own in Elementor's widget
     * panel and is easy to find without already knowing it's there.
     */

    public static function register_elementor_widgets( $widgets_manager ) {
        if ( self::$elementor_registered || ! class_exists( '\\Elementor\\Widget_Base' ) ) {
            return;
        }
        yatoon_define_elementor_widget_classes();
        foreach ( array_keys( self::surfaces() ) as $slug ) {
            $class = 'YATOON_Elementor_Widget_' . str_replace( '-', '_', $slug );
            if ( class_exists( $class ) ) {
                $widgets_manager->register( new $class() );
            }
        }
        self::$elementor_registered = true;
    }

    public static function register_elementor_widgets_legacy() {
        if ( did_action( 'elementor/widgets/register' ) || ! class_exists( '\\Elementor\\Plugin' ) ) {
            return;
        }
        self::register_elementor_widgets( \Elementor\Plugin::instance()->widgets_manager );
    }

    /** Exposed so each generated widget subclass can fetch its own surface definition by slug. */
    public static function surfaces_by_slug( $slug ) {
        $all = self::surfaces();
        return isset( $all[ $slug ] ) ? self::editor_surface( $slug, $all[ $slug ] ) : array();
    }

    /** Exposed so the widget base class above can reuse the same shortcode builder. */
    public static function render_shortcode_for( $surface, $values ) {
        return self::build_shortcode( $surface['shortcode'], $surface['atts'], $values );
    }
}

/**
 * Defines one real, named PHP class per surface, each extending
 * \Elementor\Widget_Base with its normal, untouched constructor. Only ever
 * called from YATOON_Editor_Integrations::register_elementor_widgets(),
 * which itself only runs on Elementor's own 'elementor/widgets/register'
 * hook -- so \Elementor\Widget_Base is guaranteed to already exist by the
 * time this function runs.
 *
 * An earlier version of this used a single anonymous class with a custom
 * constructor that took the surface's slug/config as extra constructor
 * arguments. That broke in real use: Elementor's own internals
 * re-instantiate widget classes directly (e.g. when building a page's
 * element tree from saved data) by calling `new $widget_class( $data,
 * $args )` with exactly Widget_Base's normal two parameters. With the old
 * custom constructor, that call landed Elementor's real widget data into
 * the wrong parameters, producing a TypeError deep inside Elementor's own
 * settings sanitizer. Named classes with the standard, unmodified
 * constructor avoid this entirely -- each one just reads its own fixed
 * surface definition internally instead of receiving it as a constructor
 * argument. This must be a plain function (not a class method) because PHP
 * does not allow declaring a class inside another class's method body.
 */
function yatoon_define_elementor_widget_classes() {
    if ( class_exists( 'YATOON_Elementor_Widget_Base' ) ) {
        return;
    }

    abstract class YATOON_Elementor_Widget_Base extends \Elementor\Widget_Base {
        /** @return array The surface definition for this specific widget. */
        abstract protected function surface();

        public function get_name() {
            return 'yatoon-' . $this->surface_slug();
        }

        public function get_title() {
            return $this->surface()['title'];
        }

        public function get_icon() {
            return $this->surface()['eicon'];
        }

        public function get_categories() {
            return array( 'yatoon' );
        }

        public function get_keywords() {
            return array( 'yatoon', 'booking', 'salon', 'appointment' );
        }

        protected function surface_slug() {
            $class  = get_class( $this );
            $prefix = 'YATOON_Elementor_Widget_';
            return str_replace( '_', '-', substr( $class, strlen( $prefix ) ) );
        }

        protected function register_controls() {
            $surface = $this->surface();
            $this->start_controls_section( 'content', array( 'label' => $surface['title'] ) );
            foreach ( $surface['atts'] as $key => $def ) {
                $control = array(
                    'label'   => $def['label'],
                    'default' => $def['default'],
                );
                if ( $def['type'] === 'number' ) {
                    $control['type'] = \Elementor\Controls_Manager::NUMBER;
                } elseif ( $def['type'] === 'boolean' ) {
                    $control['type']         = \Elementor\Controls_Manager::SWITCHER;
                    $control['label_on']     = __( 'Yes', 'yatoon-booking-system' );
                    $control['label_off']    = __( 'No', 'yatoon-booking-system' );
                    $control['return_value'] = 'yes';
                    $control['default']      = $def['default'] ? 'yes' : '';
                } elseif ( $def['type'] === 'select' ) {
                    $control['type']    = \Elementor\Controls_Manager::SELECT;
                    $control['options'] = $def['options'];
                } else {
                    $control['type'] = \Elementor\Controls_Manager::TEXT;
                }
                $this->add_control( $key, $control );
            }
            if ( empty( $surface['atts'] ) ) {
                $this->add_control(
                    'yatoon_no_options',
                    array(
                        'type' => \Elementor\Controls_Manager::RAW_HTML,
                        'raw'  => '<p class="yatoon-elementor-description">' . esc_html( $surface['description'] ) . '</p>',
                    )
                );
            }
            $this->end_controls_section();
        }

        protected function render() {
            $surface  = $this->surface();
            $settings = $this->get_settings_for_display();
            $values   = array();
            foreach ( $surface['atts'] as $key => $def ) {
                $raw = isset( $settings[ $key ] ) ? $settings[ $key ] : $def['default'];
                if ( $def['type'] === 'boolean' ) {
                    $raw = ( $raw === 'yes' || $raw === true || $raw === '1' );
                }
                $values[ $key ] = $raw;
            }
            $shortcode = YATOON_Editor_Integrations::render_shortcode_for( $surface, $values );
            echo do_shortcode( $shortcode ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- shortcode output is already escaped by its own renderer.
        }
    }

    // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound -- tiny named subclasses, one per surface; all real logic lives in the base class above.
    class YATOON_Elementor_Widget_booking extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'booking' ); }
    }
    class YATOON_Elementor_Widget_service_menu extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'service-menu' ); }
    }
    class YATOON_Elementor_Widget_gallery extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'gallery' ); }
    }
    class YATOON_Elementor_Widget_catalog extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'catalog' ); }
    }
    class YATOON_Elementor_Widget_reviews extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'reviews' ); }
    }
    class YATOON_Elementor_Widget_staff_portal extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'staff-portal' ); }
    }
    class YATOON_Elementor_Widget_customer_portal extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'customer-portal' ); }
    }
    class YATOON_Elementor_Widget_growth_storefront extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'growth-storefront' ); }
    }
    class YATOON_Elementor_Widget_manage_booking extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'manage-booking' ); }
    }
    class YATOON_Elementor_Widget_booking_popup extends YATOON_Elementor_Widget_Base {
        protected function surface() {
 return YATOON_Editor_Integrations::surfaces_by_slug( 'booking-popup' ); }
    }
    // phpcs:enable
}
