<?php
/**
 * Public shell runtime for Noesia Smart Waiter.
 *
 * This class owns only the lightweight front-end shell contract:
 * - shell shortcode entrypoints
 * - front-end screen detection
 * - shell/global public asset loading
 * - module public asset discovery for free/pro modules
 * - extension hooks around the public shell templates
 *
 * Business/domain logic remains inside modules.
 *
 * @package Noesia_Smart_Waiter
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

if ( ! class_exists( 'Noesia_SWA_Public' ) ) {
    /**
     * Front-end shell runtime.
     */
    class Noesia_SWA_Public {

        /**
         * Public shell surface: client QR/app screen.
         */
        const SURFACE_CLIENT = 'client';

        /**
         * Public shell surface: live dashboard screen.
         */
        const SURFACE_DASHBOARD = 'dashboard';

        /**
         * Cached detected surfaces.
         *
         * @var array<int,string>|null
         */
        private $detected_surfaces = null;

        /**
         * Wire public shell hooks.
         */
        public function __construct() {
            add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_public_assets' ) );
            add_shortcode( 'noesia_swa_client', array( $this, 'render_client_shortcode' ) );
            add_shortcode( 'noesia_swa_dashboard', array( $this, 'render_dashboard_shortcode' ) );
        }

        /**
         * Enqueue front-end shell assets only when a Noesia public screen exists.
         *
         * @return void
         */
        public function enqueue_public_assets() {
            if ( ! defined( 'NOESIA_SWA_PLUGIN_DIR' ) || ! defined( 'NOESIA_SWA_PLUGIN_URL' ) ) {
                return;
            }

            $surfaces = $this->get_active_surfaces();

            /**
             * Filter whether shell public assets should load for the current request.
             *
             * Useful for builders, injected templates, custom routes, or headless-like
             * integrations where the shortcode may not be present in queried content.
             *
             * @param bool               $should_enqueue Whether assets should load.
             * @param array<int,string>  $surfaces       Detected public surfaces.
             * @param Noesia_SWA_Public  $this           Public shell instance.
             */
            $should_enqueue = (bool) apply_filters(
                'noesia_swa_public_should_enqueue_shell',
                ! empty( $surfaces ),
                $surfaces,
                $this
            );

            if ( ! $should_enqueue ) {
                return;
            }

            $shell_assets = $this->get_shell_asset_urls();

            if ( ! empty( $shell_assets['css'] ) ) {
                wp_enqueue_style(
                    'noesia-public-css',
                    $shell_assets['css'],
                    array(),
                    $this->get_shell_asset_version( 'css' )
                );

                $this->enqueue_dynamic_public_colors();
            }

            if ( ! empty( $shell_assets['js'] ) ) {
                wp_enqueue_script(
                    'noesia-public-js',
                    $shell_assets['js'],
                    array( 'jquery' ),
                    $this->get_shell_asset_version( 'js' ),
                    true
                );
            }

            $module_manifest = $this->get_module_public_asset_manifest();
            $active_modules  = $this->get_active_public_modules( $surfaces );

            $this->enqueue_active_module_public_assets( $active_modules, $module_manifest );

            if ( wp_script_is( 'noesia-public-js', 'enqueued' ) ) {
                wp_localize_script(
                    'noesia-public-js',
                    'noesia_vars',
                    $this->get_public_js_payload( $surfaces, $active_modules, $module_manifest )
                );
            }
        }

        /**
         * Shortcode: [noesia_swa_client]
         *
         * @param array $atts Shortcode attributes.
         * @return string
         */
        public function render_client_shortcode( $atts ) {
            if ( ! defined( 'NOESIA_SWA_PLUGIN_DIR' ) ) {
                return '';
            }

            $atts = shortcode_atts(
                array(
                    'location' => 'Table',
                ),
                (array) $atts,
                'noesia_swa_client'
            );

            $atts['location'] = sanitize_text_field( (string) $atts['location'] );

            /**
             * Filter the client shortcode attributes before rendering.
             *
             * @param array<string,string> $atts Client shortcode attributes.
             * @param Noesia_SWA_Public    $this Public shell instance.
             */
            $atts = apply_filters( 'noesia_swa_public_client_atts', $atts, $this );

            ob_start();
            include NOESIA_SWA_PLUGIN_DIR . 'public/partials/client-display.php';
            return (string) ob_get_clean();
        }

        /**
         * Shortcode: [noesia_swa_dashboard]
         *
         * @param array $atts Shortcode attributes.
         * @return string
         */
        public function render_dashboard_shortcode( $atts ) {
            if ( ! defined( 'NOESIA_SWA_PLUGIN_DIR' ) ) {
                return '';
            }

            $atts = shortcode_atts(
                array(
                    'demo' => 'false',
                ),
                (array) $atts,
                'noesia_swa_dashboard'
            );

            $demo_raw = strtolower( sanitize_text_field( (string) $atts['demo'] ) );
            $is_demo  = ( 'true' === $demo_raw );
            $is_admin = current_user_can( 'manage_options' );

            /**
             * Filter dashboard shortcode authorization.
             *
             * @param bool               $authorized Whether the current user/request is authorized.
             * @param array<string,string> $atts     Dashboard shortcode attributes.
             * @param bool               $is_demo    Whether demo mode is requested.
             * @param Noesia_SWA_Public  $this       Public shell instance.
             */
            $is_authorized = (bool) apply_filters(
                'noesia_swa_public_dashboard_is_authorized',
                ( $is_admin || $is_demo ),
                $atts,
                $is_demo,
                $this
            );

            if ( ! $is_authorized ) {
                return '<p>' . esc_html__( 'Authorized Personnel Only.', 'noesia-smart-waiter' ) . '</p>';
            }

            /**
             * Filter the dashboard shortcode attributes before rendering.
             *
             * @param array<string,string> $atts Dashboard shortcode attributes.
             * @param Noesia_SWA_Public    $this Public shell instance.
             */
            $atts = apply_filters( 'noesia_swa_public_dashboard_atts', $atts, $this );

            ob_start();
            include NOESIA_SWA_PLUGIN_DIR . 'public/partials/dashboard-display.php';
            return (string) ob_get_clean();
        }

        /**
         * Detect active public shell surfaces for the current request.
         *
         * @return array<int,string>
         */
        public function get_active_surfaces() {
            if ( is_array( $this->detected_surfaces ) ) {
                return $this->detected_surfaces;
            }

            $surfaces = array();
            $content  = $this->get_queried_content_for_shortcode_scan();

            if ( '' !== $content ) {
                if ( has_shortcode( $content, 'noesia_swa_client' ) ) {
                    $surfaces[] = self::SURFACE_CLIENT;
                }

                if ( has_shortcode( $content, 'noesia_swa_dashboard' ) ) {
                    $surfaces[] = self::SURFACE_DASHBOARD;
                }
            }

            /**
             * Allow custom/public modules to declare active shell surfaces.
             *
             * @param array<int,string> $surfaces Currently detected surfaces.
             * @param string            $content  Queried content inspected by the shell.
             * @param Noesia_SWA_Public $this     Public shell instance.
             */
            $surfaces = apply_filters( 'noesia_swa_public_active_surfaces', $surfaces, $content, $this );

            if ( ! is_array( $surfaces ) ) {
                $surfaces = array();
            }

            $surfaces = array_values(
                array_unique(
                    array_filter(
                        array_map( 'sanitize_key', $surfaces ),
                        array( $this, 'is_valid_surface' )
                    )
                )
            );

            $this->detected_surfaces = $surfaces;

            return $this->detected_surfaces;
        }

        /**
         * Validate a shell surface slug.
         *
         * @param string $surface Surface slug.
         * @return bool
         */
        private function is_valid_surface( $surface ) {
            $surface = sanitize_key( (string) $surface );

            return in_array( $surface, array( self::SURFACE_CLIENT, self::SURFACE_DASHBOARD ), true );
        }

        /**
         * Get the current queried content for shortcode scanning.
         *
         * @return string
         */
        private function get_queried_content_for_shortcode_scan() {
            $content = '';

            if ( is_singular() ) {
                $queried_object = get_queried_object();

                if ( $queried_object instanceof WP_Post ) {
                    $content = (string) $queried_object->post_content;
                }
            }

            /**
             * Filter queried content used for shortcode surface detection.
             *
             * @param string            $content Detected content.
             * @param Noesia_SWA_Public $this    Public shell instance.
             */
            $content = apply_filters( 'noesia_swa_public_detect_content', $content, $this );

            return is_string( $content ) ? $content : '';
        }

        /**
         * Enqueue CSS variables for dynamic public colors.
         *
         * @return void
         */
        private function enqueue_dynamic_public_colors() {
            $opt = get_option( 'noesia_swa_options', array() );

            if ( ! is_array( $opt ) ) {
                $opt = array();
            }

            $col_bg    = ! empty( $opt['color_bg'] ) ? sanitize_hex_color( (string) $opt['color_bg'] ) : '';
            $col_title = ! empty( $opt['color_title'] ) ? sanitize_hex_color( (string) $opt['color_title'] ) : '';
            $col_text  = ! empty( $opt['color_text'] ) ? sanitize_hex_color( (string) $opt['color_text'] ) : '';

            if ( empty( $col_bg ) ) {
                $col_bg = '#121212';
            }
            if ( empty( $col_title ) ) {
                $col_title = '#ffffff';
            }
            if ( empty( $col_text ) ) {
                $col_text = '#ffffff';
            }

            $custom_css = ':root { --ns-dyn-bg: ' . $col_bg . '; --ns-dyn-title: ' . $col_title . '; --ns-dyn-text: ' . $col_text . '; }';
            wp_add_inline_style( 'noesia-public-css', $custom_css );
        }

        /**
         * Get the shell public asset URLs.
         *
         * @return array<string,string>
         */
        private function get_shell_asset_urls() {
            $assets = array();
            $css    = NOESIA_SWA_PLUGIN_DIR . 'public/css/noesia-public.css';
            $js     = NOESIA_SWA_PLUGIN_DIR . 'public/js/noesia-public.js';

            if ( file_exists( $css ) ) {
                $assets['css'] = NOESIA_SWA_PLUGIN_URL . 'public/css/noesia-public.css';
            }

            if ( file_exists( $js ) ) {
                $assets['js'] = NOESIA_SWA_PLUGIN_URL . 'public/js/noesia-public.js';
            }

            /**
             * Filter the shell public asset URLs.
             *
             * @param array<string,string> $assets Shell asset URLs.
             * @param Noesia_SWA_Public    $this   Public shell instance.
             */
            $assets = apply_filters( 'noesia_swa_public_shell_assets', $assets, $this );

            return is_array( $assets ) ? $assets : array();
        }

        /**
         * Get shell asset version by filemtime fallback.
         *
         * @param string $type Asset type.
         * @return string|bool
         */
        private function get_shell_asset_version( $type ) {
            $type = sanitize_key( (string) $type );
            $path = '';

            if ( 'css' === $type ) {
                $path = NOESIA_SWA_PLUGIN_DIR . 'public/css/noesia-public.css';
            } elseif ( 'js' === $type ) {
                $path = NOESIA_SWA_PLUGIN_DIR . 'public/js/noesia-public.js';
            }

            if ( $path && file_exists( $path ) ) {
                return (string) filemtime( $path );
            }

            return defined( 'NOESIA_SWA_VERSION' ) ? NOESIA_SWA_VERSION : false;
        }

        /**
         * Return roots that may contain module-local public assets.
         *
         * @return array<int,array<string,string>>
         */
        private function get_module_asset_roots() {
            $roots = array(
                array(
                    'dir' => NOESIA_SWA_PLUGIN_DIR . 'modules/free',
                    'url' => NOESIA_SWA_PLUGIN_URL . 'modules/free',
                ),
            );

            $pro_enabled = (bool) apply_filters( 'noesia_swa_pro_enabled', false );

            if ( $pro_enabled && is_dir( NOESIA_SWA_PLUGIN_DIR . 'pro/modules' ) ) {
                $roots[] = array(
                    'dir' => NOESIA_SWA_PLUGIN_DIR . 'pro/modules',
                    'url' => NOESIA_SWA_PLUGIN_URL . 'pro/modules',
                );
            }

            /**
             * Filter public module asset roots.
             *
             * @param array<int,array<string,string>> $roots Public module roots.
             * @param Noesia_SWA_Public               $this  Public shell instance.
             */
            $roots = apply_filters( 'noesia_swa_public_module_asset_roots', $roots, $this );

            return is_array( $roots ) ? $roots : array();
        }

        /**
         * Build a public asset manifest for folder-based modules.
         *
         * Expected self-contained module files:
         * - assets/css/public.css
         * - assets/js/public.js
         *
         * @return array<string,array<string,string>>
         */
        public function get_module_public_asset_manifest() {
            $manifest    = array();
            $asset_roots = $this->get_module_asset_roots();

            foreach ( $asset_roots as $asset_root ) {
                if ( empty( $asset_root['dir'] ) || empty( $asset_root['url'] ) || ! is_dir( $asset_root['dir'] ) ) {
                    continue;
                }

                $module_dirs = glob( trailingslashit( $asset_root['dir'] ) . '*', GLOB_ONLYDIR );

                if ( empty( $module_dirs ) || ! is_array( $module_dirs ) ) {
                    continue;
                }

                foreach ( $module_dirs as $module_dir ) {
                    $module_slug = sanitize_key( basename( (string) $module_dir ) );

                    if ( '' === $module_slug ) {
                        continue;
                    }

                    $entry   = array();
                    $css     = trailingslashit( $module_dir ) . 'assets/css/public.css';
                    $js      = trailingslashit( $module_dir ) . 'assets/js/public.js';
                    $base_url = trailingslashit( $asset_root['url'] ) . $module_slug . '/assets/';

                    if ( file_exists( $css ) ) {
                        $entry['css'] = $base_url . 'css/public.css?v=' . rawurlencode( (string) filemtime( $css ) );
                    }

                    if ( file_exists( $js ) ) {
                        $entry['js'] = $base_url . 'js/public.js?v=' . rawurlencode( (string) filemtime( $js ) );
                    }

                    if ( ! empty( $entry ) ) {
                        $manifest[ $module_slug ] = $entry;
                    }
                }
            }

            /**
             * Filter the public module asset manifest.
             *
             * @param array<string,array<string,string>> $manifest Public asset manifest.
             * @param Noesia_SWA_Public                  $this     Public shell instance.
             */
            $manifest = apply_filters( 'noesia_swa_public_module_asset_manifest', $manifest, $this );

            return is_array( $manifest ) ? $manifest : array();
        }

        /**
         * Resolve active module slugs for the current public request.
         *
         * Default behavior keeps backward compatibility by loading the Design
         * module as the current owner of the free public request/dashboard flow.
         * Future Pro modules can extend this contract through filters and public
         * shell hooks without touching the kernel.
         *
         * @param array<int,string> $surfaces Active shell surfaces.
         * @return array<int,string>
         */
        public function get_active_public_modules( $surfaces ) {
            $surfaces = is_array( $surfaces ) ? $surfaces : array();
            $modules  = array();

            foreach ( $surfaces as $surface ) {
                $surface = sanitize_key( (string) $surface );

                if ( ! $this->is_valid_surface( $surface ) ) {
                    continue;
                }

                $default_modules = array( 'design' );

                /**
                 * Filter the default module owners for a specific public surface.
                 *
                 * @param array<int,string> $default_modules Default modules for the surface.
                 * @param string            $surface         Active surface slug.
                 * @param Noesia_SWA_Public $this            Public shell instance.
                 */
                $surface_modules = apply_filters(
                    'noesia_swa_public_surface_modules',
                    $default_modules,
                    $surface,
                    $this
                );

                if ( is_array( $surface_modules ) ) {
                    $modules = array_merge( $modules, $surface_modules );
                }
            }

            /**
             * Filter the final active public modules list.
             *
             * @param array<int,string> $modules  Active public module slugs.
             * @param array<int,string> $surfaces Active shell surfaces.
             * @param Noesia_SWA_Public $this     Public shell instance.
             */
            $modules = apply_filters( 'noesia_swa_public_active_modules', $modules, $surfaces, $this );

            if ( ! is_array( $modules ) ) {
                $modules = array();
            }

            return array_values( array_unique( array_filter( array_map( 'sanitize_key', $modules ) ) ) );
        }

        /**
         * Enqueue module-local public assets for active modules only.
         *
         * @param array<int,string>                 $active_modules Active module IDs.
         * @param array<string,array<string,string>> $manifest      Public asset manifest.
         * @return void
         */
        private function enqueue_active_module_public_assets( $active_modules, $manifest ) {
            if ( ! is_array( $active_modules ) || ! is_array( $manifest ) ) {
                return;
            }

            foreach ( $active_modules as $module_slug ) {
                $module_slug = sanitize_key( (string) $module_slug );

                if ( '' === $module_slug || empty( $manifest[ $module_slug ] ) || ! is_array( $manifest[ $module_slug ] ) ) {
                    continue;
                }

                $entry = $manifest[ $module_slug ];

                if ( ! empty( $entry['css'] ) ) {
                    wp_enqueue_style(
                        'noesia-swa-public-module-' . $module_slug,
                        $entry['css'],
                        array( 'noesia-public-css' ),
                        defined( 'NOESIA_SWA_VERSION' ) ? NOESIA_SWA_VERSION : false
                    );
                }

                if ( ! empty( $entry['js'] ) ) {
                    wp_enqueue_script(
                        'noesia-swa-public-module-' . $module_slug,
                        $entry['js'],
                        array( 'jquery', 'noesia-public-js' ),
                        defined( 'NOESIA_SWA_VERSION' ) ? NOESIA_SWA_VERSION : false,
                        true
                    );
                }
            }
        }

        /**
         * Build the shared localized payload for the public shell.
         *
         * @param array<int,string>                  $surfaces       Active shell surfaces.
         * @param array<int,string>                  $active_modules Active public modules.
         * @param array<string,array<string,string>> $module_manifest Module asset manifest.
         * @return array<string,mixed>
         */
        private function get_public_js_payload( $surfaces, $active_modules, $module_manifest ) {
            $payload = array(
                'ajax_url'          => admin_url( 'admin-ajax.php' ),
                'nonce'             => wp_create_nonce( 'noesia_swa_nonce' ),
                'lbl_sound_on'      => __( '🔊 Sound On', 'noesia-smart-waiter' ),
                'lbl_sound_off'     => __( '🔇 Sound Off', 'noesia-smart-waiter' ),
                'msg_conn_error'    => __( '❌ Connection Error', 'noesia-smart-waiter' ),
                'msg_timeout'       => __( '❌ Server Timeout', 'noesia-smart-waiter' ),
                'msg_all_quiet'     => __( 'All quiet... ☕', 'noesia-smart-waiter' ),
                'btn_complete'      => __( '✓ Complete', 'noesia-smart-waiter' ),
                'lbl_unknown'       => __( 'Unknown', 'noesia-smart-waiter' ),
                'msg_error_gen'     => __( '❌ Error', 'noesia-smart-waiter' ),
                'msg_audio_blocked' => __( 'Audio blocked. Click OK to enable.', 'noesia-smart-waiter' ),
                'active_surfaces'   => array_values( array_unique( array_filter( array_map( 'sanitize_key', (array) $surfaces ) ) ) ),
                'active_modules'    => array_values( array_unique( array_filter( array_map( 'sanitize_key', (array) $active_modules ) ) ) ),
                'module_assets'     => is_array( $module_manifest ) ? $module_manifest : array(),
            );

            /**
             * Filter the public shell JS payload.
             *
             * @param array<string,mixed> $payload Public shell payload.
             * @param Noesia_SWA_Public   $this    Public shell instance.
             */
            $payload = apply_filters( 'noesia_swa_public_js_payload', $payload, $this );

            return is_array( $payload ) ? $payload : array();
        }
    }
}
