<?php
/**
 * Admin bootstrap and shared shell handling for Noesia Smart Waiter.
 *
 * Kernel-only responsibilities:
 * - admin menu
 * - register_setting
 * - shared shell assets
 * - menu visibility persistence
 * - module asset manifest
 *
 * Module-specific defaults, sanitization, localized options, and translations
 * must be injected through filters by the modules themselves.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_SWA_Admin' ) ) {
    /**
     * Handles the shared admin shell.
     */
    class Noesia_SWA_Admin {

        /**
         * Main plugin file path.
         *
         * @var string
         */
        private $plugin_file = '';

        /**
         * Plugin basename used for action links.
         *
         * @var string
         */
        private $plugin_basename = '';

        /**
         * Constructor.
         *
         * @param string $plugin_file Main plugin file path.
         */
        public function __construct( $plugin_file ) {
            $this->plugin_file     = is_string( $plugin_file ) ? $plugin_file : '';
            $this->plugin_basename = $this->plugin_file ? plugin_basename( $this->plugin_file ) : '';

            add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
            add_action( 'admin_init', array( $this, 'register_settings' ) );
            add_action( 'admin_init', array( $this, 'maybe_upgrade_options' ), 5 );
            add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
            add_action( 'wp_ajax_noesia_swa_save_menu_state', array( $this, 'handle_save_menu_state' ) );
            add_action( 'wp_ajax_noesia_swa_render_admin_section', array( $this, 'handle_render_admin_section' ) );

            if ( $this->plugin_basename ) {
                add_filter(
                    'plugin_action_links_' . $this->plugin_basename,
                    array( $this, 'add_plugin_action_links' )
                );
            }
        }

        /**
         * Register the admin menu page.
         *
         * @return void
         */
        public function add_admin_menu() {
            add_menu_page(
                __( 'Smart Waiter', 'noesia-smart-waiter' ),
                __( 'Smart Waiter', 'noesia-smart-waiter' ),
                'manage_options',
                'noesia_swa_settings',
                array( $this, 'render_settings_page' ),
                'dashicons-store',
                81
            );
        }

        /**
         * Register plugin settings.
         *
         * @return void
         */
        public function register_settings() {
            register_setting(
                'noesia_swa_options_group',
                'noesia_swa_options',
                array( $this, 'sanitize_options' )
            );
        }

        /**
         * Add missing defaults for upgraded installs.
         *
         * Kernel owns only core/shared defaults. Modules must inject their own
         * defaults through the filter.
         *
         * @return void
         */
        public function maybe_upgrade_options() {
            $options = get_option( 'noesia_swa_options', array() );

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

            $defaults = $this->get_kernel_option_defaults();

            /**
             * Allow modules to provide their own defaults.
             *
             * @param array<string,mixed> $defaults Kernel defaults.
             * @param array<string,mixed> $options  Existing saved options.
             * @param Noesia_SWA_Admin    $this     Admin instance.
             */
            $defaults = apply_filters( 'noesia_swa_option_defaults', $defaults, $options, $this );

            if ( ! is_array( $defaults ) ) {
                $defaults = $this->get_kernel_option_defaults();
            }

            $needs_update = false;

            foreach ( $defaults as $key => $value ) {
                $key = sanitize_key( (string) $key );

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

                if ( ! array_key_exists( $key, $options ) ) {
                    $options[ $key ] = $value;
                    $needs_update    = true;
                }
            }

            if ( $needs_update ) {
                update_option( 'noesia_swa_options', $options );
            }
        }

        /**
         * Kernel-only option defaults.
         *
         * @return array<string,mixed>
         */
        private function get_kernel_option_defaults() {
            return array(
                'menu_hidden_sections' => '[]',
                'dock_shortcuts_v1'   => '[]',
            );
        }

        /**
         * Save hidden menu sections through AJAX.
         *
         * @return void
         */
        public function handle_save_menu_state() {
            if ( ! $this->is_valid_menu_state_request() ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'Your wizard session expired. Please refresh and try again.', 'noesia-smart-waiter' ),
                    ),
                    403
                );
            }

            if ( ! current_user_can( 'manage_options' ) ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'You are not allowed to do that.', 'noesia-smart-waiter' ),
                    ),
                    403
                );
            }

            $hidden_sections = $this->get_posted_hidden_sections();

            $options = get_option( 'noesia_swa_options', array() );

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

            $options['menu_hidden_sections'] = wp_json_encode( $hidden_sections );
            update_option( 'noesia_swa_options', $options );

            wp_send_json_success(
                array(
                    'hidden_sections' => $hidden_sections,
                    'encoded'         => $options['menu_hidden_sections'],
                )
            );
        }

        /**
         * Validate menu-state AJAX requests.
         *
         * The wizard and Menu Visibility Manager can both save the same option.
         * Prefer nonce validation, then keep a narrow admin-screen fallback for
         * upgraded installs or cached wizard assets that still originate from
         * the Smart Waiter admin surface.
         *
         * @return bool
         */
        private function is_valid_menu_state_request() {
	$request_method = isset( $_SERVER['REQUEST_METHOD'] )
		? strtoupper( sanitize_text_field( wp_unslash( (string) $_SERVER['REQUEST_METHOD'] ) ) )
		: '';

	if ( 'POST' !== $request_method ) {
		return false;
	}

	return false !== check_ajax_referer( 'noesia_swa_nonce', 'nonce', false );
}

        /**
         * Parse and sanitize posted hidden section IDs.
         *
         * @return array<int,string>
         */
        private function get_posted_hidden_sections() {
            $raw_hidden = $this->get_unslashed_post_value( 'hidden_sections' );

            if ( null === $raw_hidden ) {
                return array();
            }

            if ( is_array( $raw_hidden ) ) {
                $hidden_sections = array();

                foreach ( $raw_hidden as $section_id ) {
                    if ( ! is_scalar( $section_id ) && null !== $section_id ) {
                        continue;
                    }

                    $hidden_sections[] = sanitize_key( (string) $section_id );
                }
            } else {
                $hidden_sections = $this->decode_hidden_sections_payload( (string) $raw_hidden );
            }

            return array_values( array_unique( array_filter( $hidden_sections ) ) );
        }

        /**
         * Decode hidden section payloads from JSON or comma-separated strings.
         *
         * @param string $raw_hidden Raw hidden section payload.
         * @return array<int,string>
         */
        private function decode_hidden_sections_payload( $raw_hidden ) {
            $decoded = json_decode( $raw_hidden, true );

            if ( is_array( $decoded ) ) {
                return array_map( 'sanitize_key', $decoded );
            }

            $parts = array_filter( array_map( 'trim', explode( ',', $raw_hidden ) ) );

            return array_map( 'sanitize_key', $parts );
        }

        /**
         * Read an unslashed POST value when a verified admin request needs it.
         *
         * @param string $key POST field name.
         * @return mixed|null
         */
        private function get_unslashed_post_value( $key ) {
	$request_key = sanitize_key( (string) $key );

	if ( '' === $request_key ) {
		return null;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Called only after nonce verification in the owning request handler.
	if ( ! isset( $_POST[ $request_key ] ) ) {
		return null;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Called only after nonce verification; scalar values are sanitized before being returned and arrays are sanitized by the owning caller.
$value = wp_unslash( $_POST[ $request_key ] );

	if ( is_array( $value ) ) {
		return $value;
	}

	if ( ! is_scalar( $value ) ) {
		return null;
	}

	return sanitize_text_field( (string) $value );
}

        /**
         * Read and normalize a POST field expected to contain a section key.
         *
         * @param string $key POST field name.
         * @return string
         */
        private function get_sanitized_post_key( $key ) {
            $value = $this->get_unslashed_post_value( $key );

            if ( ! is_string( $value ) ) {
                return '';
            }

            return sanitize_key( $value );
        }

        /**
         * Render one admin section over AJAX for true lazy loading.
         *
         * @return void
         */
        public function handle_render_admin_section() {
            check_ajax_referer( 'noesia_swa_nonce', 'nonce' );

            if ( ! current_user_can( 'manage_options' ) ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'You are not allowed to do that.', 'noesia-smart-waiter' ),
                    ),
                    403
                );
            }

            $requested_section = $this->get_sanitized_post_key( 'section' );

            $registry = $this->get_admin_sections_registry();

            if ( ! $registry instanceof Noesia_SWA_Admin_Sections ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'Admin sections registry unavailable.', 'noesia-smart-waiter' ),
                    ),
                    500
                );
            }

            $section_id = $registry->resolve_active_section( $requested_section, 'dashboard' );
            $section    = $registry->get_section( $section_id );

            if ( empty( $section ) || empty( $section['is_visible'] ) ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'The requested section is not available.', 'noesia-smart-waiter' ),
                    ),
                    404
                );
            }

            ob_start();
            $registry->render_section( $section_id );
            $html = ob_get_clean();

            wp_send_json_success(
                array(
                    'section' => $section_id,
                    'title'   => isset( $section['label'] ) ? sanitize_text_field( (string) $section['label'] ) : ucfirst( str_replace( '-', ' ', $section_id ) ),
                    'html'    => is_string( $html ) ? $html : '',
                )
            );
        }

        /**
         * Sanitize plugin settings.
         *
         * Kernel sanitizes only kernel-owned fields. Modules must sanitize their
         * own fields through filters.
         *
         * @param mixed $input Raw input from the settings form.
         * @return array<string,mixed>
         */
        public function sanitize_options( $input ) {
            $existing = get_option( 'noesia_swa_options', array() );

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

            $new_input = $existing;

            if ( ! is_array( $input ) ) {
                return $new_input;
            }

            if ( isset( $input['menu_hidden_sections'] ) ) {
                $new_input['menu_hidden_sections'] = $this->sanitize_menu_hidden_sections( $input['menu_hidden_sections'] );
            }

            if ( isset( $input['dock_shortcuts_v1'] ) ) {
                $new_input['dock_shortcuts_v1'] = $this->sanitize_dock_shortcuts( $input['dock_shortcuts_v1'] );
            }

            /**
             * Allow modules to sanitize and persist the fields they own.
             *
             * @param array<string,mixed> $new_input Sanitized output so far.
             * @param array<string,mixed> $input     Raw submitted input.
             * @param array<string,mixed> $existing  Previously saved options.
             * @param Noesia_SWA_Admin    $this      Admin instance.
             */
            $new_input = apply_filters( 'noesia_swa_sanitize_options', $new_input, $input, $existing, $this );

            if ( ! is_array( $new_input ) ) {
                return $existing;
            }

            return $new_input;
        }

        /**
         * Sanitize the hidden menu sections setting.
         *
         * @param mixed $value Raw submitted value.
         * @return string JSON-encoded array.
         */

        /**
         * Sanitize dock shortcuts JSON payload.
         *
         * @param mixed $value Raw submitted value.
         * @return string JSON-encoded array.
         */
        private function sanitize_dock_shortcuts( $value ) {
            $decoded = array();

            if ( is_array( $value ) ) {
                $decoded = $value;
            } else {
                $raw = wp_unslash( (string) $value );
                $json = json_decode( $raw, true );
                $decoded = is_array( $json ) ? $json : array();
            }

            if ( empty( $decoded ) || ! is_array( $decoded ) ) {
                return '[]';
            }

            $allowed_types      = array( 'internal', 'web', 'external' );
            $allowed_icon_modes = array( 'builtin', 'upload' );
            $allowed_icons      = array(
                'chrome',
                'filezilla',
                'viber',
                'whatsapp',
                'gmail',
                'google-drive',
                'davinci-resolve',
                'youtube',
                'facebook',
                'instagram',
                'tiktok',
                'github',
                'stack-overflow',
            );
            $sanitized = array();

            foreach ( $decoded as $item ) {
                if ( ! is_array( $item ) ) {
                    continue;
                }

                $id           = isset( $item['id'] ) ? sanitize_key( (string) $item['id'] ) : '';
                $label        = isset( $item['label'] ) ? sanitize_text_field( wp_unslash( (string) $item['label'] ) ) : '';
                $type         = isset( $item['type'] ) ? sanitize_key( (string) $item['type'] ) : 'web';
                $target       = isset( $item['target'] ) ? sanitize_text_field( wp_unslash( (string) $item['target'] ) ) : '';
                $internal_key = isset( $item['internal_target'] ) ? sanitize_key( (string) $item['internal_target'] ) : '';
                $icon_mode    = isset( $item['icon_mode'] ) ? sanitize_key( (string) $item['icon_mode'] ) : 'builtin';
                $icon_key     = isset( $item['icon_key'] ) ? sanitize_key( (string) $item['icon_key'] ) : 'chrome';
                $icon_url     = isset( $item['icon_url'] ) ? esc_url_raw( wp_unslash( (string) $item['icon_url'] ) ) : '';
                $enabled      = ! empty( $item['enabled'] );

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

                if ( ! in_array( $type, $allowed_types, true ) ) {
                    $type = 'web';
                }

                if ( ! in_array( $icon_mode, $allowed_icon_modes, true ) ) {
                    $icon_mode = 'builtin';
                }

                if ( ! in_array( $icon_key, $allowed_icons, true ) ) {
                    $icon_key = 'chrome';
                }

                if ( '' === $id ) {
                    $id = 'dock-shortcut-' . substr( md5( $label . $type . $target . $internal_key ), 0, 12 );
                }

                if ( 'internal' === $type ) {
                    $target = '';
                } else {
                    $internal_key = '';
                }

                if ( 'upload' !== $icon_mode ) {
                    $icon_url = '';
                }

                if ( 'upload' === $icon_mode && '' === $icon_url ) {
                    $icon_mode = 'builtin';
                }

                $sanitized[] = array(
                    'id'              => $id,
                    'label'           => $label,
                    'type'            => $type,
                    'target'          => $target,
                    'internal_target' => $internal_key,
                    'icon_mode'       => $icon_mode,
                    'icon_key'        => $icon_key,
                    'icon_url'        => $icon_url,
                    'enabled'         => $enabled,
                );
            }

            return wp_json_encode( array_values( $sanitized ) );
        }

        private function sanitize_menu_hidden_sections( $value ) {
            $hidden_sections = array();

            if ( is_array( $value ) ) {
                $hidden_sections = array_map( 'sanitize_key', wp_unslash( $value ) );
            } else {
                $raw_hidden = wp_unslash( (string) $value );
                $decoded    = json_decode( $raw_hidden, true );

                if ( is_array( $decoded ) ) {
                    $hidden_sections = array_map( 'sanitize_key', $decoded );
                } else {
                    $parts           = array_filter( array_map( 'trim', explode( ',', $raw_hidden ) ) );
                    $hidden_sections = array_map( 'sanitize_key', $parts );
                }
            }

            $hidden_sections = array_values( array_unique( array_filter( $hidden_sections ) ) );

            return wp_json_encode( $hidden_sections );
        }

        /**
         * Enqueue shared admin assets for the plugin screen.
         *
         * @param string $hook Current admin page hook.
         * @return void
         */
        public function enqueue_admin_assets( $hook ) {
            if ( 'toplevel_page_noesia_swa_settings' !== (string) $hook ) {
                return;
            }

            $module_assets  = $this->get_module_asset_manifest();
            $section_titles = $this->get_section_title_map();
            $options        = get_option( 'noesia_swa_options', array() );

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

            /**
             * Allow modules to expose only the options they need in JS.
             *
             * @param array<string,mixed> $options Current option payload.
             * @param Noesia_SWA_Admin    $this    Admin instance.
             */
            $options = apply_filters( 'noesia_swa_localized_options', $options, $this );

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

            wp_enqueue_media();

            wp_enqueue_style(
                'noesia-admin-css',
                NOESIA_SWA_PLUGIN_URL . 'admin/css/noesia-admin.css',
                array(),
                NOESIA_SWA_VERSION
            );

            wp_enqueue_script(
                'noesia-qrcode-lib',
                NOESIA_SWA_PLUGIN_URL . 'admin/js/qrcode.min.js',
                array(),
                '1.0.0',
                true
            );

            wp_enqueue_script(
                'noesia-admin-js',
                NOESIA_SWA_PLUGIN_URL . 'admin/js/noesia-admin.js',
                array( 'jquery', 'noesia-qrcode-lib' ),
                NOESIA_SWA_VERSION,
                true
            );

            $shell_strings = array();

            /**
             * Allow modules to append JS strings to the shared admin payload.
             *
             * Kernel now ships no module-specific JS strings by default.
             *
             * @param array<string,string> $shell_strings Empty kernel string map.
             * @param Noesia_SWA_Admin     $this          Admin instance.
             */
            $shell_strings = apply_filters( 'noesia_swa_admin_strings', $shell_strings, $this );

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

            $shell_i18n = array();

            /**
             * Allow modules to append JS i18n labels to the shared admin payload.
             *
             * Kernel intentionally ships no module-specific labels by default.
             *
             * @param array<string,string> $shell_i18n Empty kernel i18n map.
             * @param Noesia_SWA_Admin     $this       Admin instance.
             */
            $shell_i18n = apply_filters( 'noesia_swa_admin_i18n', $shell_i18n, $this );

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

            wp_localize_script(
                'noesia-admin-js',
                'noesia_admin_vars',
                array(
                    'ajax_url'      => admin_url( 'admin-ajax.php' ),
                    'nonce'         => wp_create_nonce( 'noesia_swa_nonce' ),
                    'nonce_reset'   => wp_create_nonce( 'noesia_swa_reset_nonce' ),
                    'plugin_url'    => trailingslashit( NOESIA_SWA_PLUGIN_URL ),
                    'module_assets' => $module_assets,
                    'section_titles'=> $section_titles,
                    'options'       => $options,
                    'strings'       => $shell_strings,
                    'i18n'          => $shell_i18n,
                )
            );
        }

        /**
         * Resolve the admin sections registry from the main plugin helper.
         *
         * @return Noesia_SWA_Admin_Sections|null
         */
        private function get_admin_sections_registry() {
            if ( function_exists( 'noesia_swa' ) ) {
                $plugin = noesia_swa();

                if (
                    is_object( $plugin ) &&
                    isset( $plugin->admin_sections ) &&
                    $plugin->admin_sections instanceof Noesia_SWA_Admin_Sections
                ) {
                    return $plugin->admin_sections;
                }
            }

            return null;
        }

        /**
         * Build the shared section-title map for shell routing.
         *
         * @return array<string,string>
         */
        private function get_section_title_map() {
            $titles   = array();
            $registry = $this->get_admin_sections_registry();

            if ( ! $registry instanceof Noesia_SWA_Admin_Sections ) {
                return $titles;
            }

            foreach ( $registry->get_sections() as $section_id => $section ) {
                $section_id = sanitize_key( (string) $section_id );

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

                $titles[ $section_id ] = isset( $section['label'] )
                    ? sanitize_text_field( (string) $section['label'] )
                    : ucfirst( str_replace( '-', ' ', $section_id ) );
            }

            return $titles;
        }

        /**
         * Return the module roots that may contain module-local assets.
         *
         * @return array<int, array<string, string>>
         */
        private function get_module_asset_roots() {
            $roots   = array();
            $manager = null;

            if ( function_exists( 'noesia_swa' ) ) {
                $plugin = noesia_swa();

                if ( is_object( $plugin ) && isset( $plugin->modules ) && $plugin->modules instanceof Noesia_SWA_Modules ) {
                    $manager = $plugin->modules;
                }
            }

            if ( $manager instanceof Noesia_SWA_Modules ) {
                $paths = $manager->get_discovery_paths();

                foreach ( $paths as $tier => $tier_paths ) {
                    if ( empty( $tier_paths ) || ! is_array( $tier_paths ) ) {
                        continue;
                    }

                    foreach ( $tier_paths as $path ) {
                        $root = $this->build_module_asset_root( $path, $tier );

                        if ( ! empty( $root ) ) {
                            $roots[] = $root;
                        }
                    }
                }
            }

            if ( empty( $roots ) ) {
                $roots[] = array(
                    'tier' => 'free',
                    'dir'  => trailingslashit( NOESIA_SWA_PLUGIN_DIR . 'modules/free' ),
                    'url'  => trailingslashit( 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(
                        'tier' => 'pro',
                        'dir'  => trailingslashit( NOESIA_SWA_PLUGIN_DIR . 'pro/modules' ),
                        'url'  => trailingslashit( NOESIA_SWA_PLUGIN_URL . 'pro/modules' ),
                    );
                }
            }

            /**
             * Filter admin asset roots used for lazy module CSS/JS manifests.
             *
             * @param array                         $roots   Asset root records.
             * @param Noesia_SWA_Modules|null      $manager Module manager instance when available.
             * @param Noesia_SWA_Admin             $this    Admin shell instance.
             */
            $roots = apply_filters( 'noesia_swa_admin_asset_roots', $roots, $manager, $this );

            if ( ! is_array( $roots ) ) {
                return array();
            }

            $normalized = array();

            foreach ( $roots as $root ) {
                if ( empty( $root['dir'] ) || empty( $root['url'] ) ) {
                    continue;
                }

                $dir = trailingslashit( (string) $root['dir'] );
                $url = trailingslashit( (string) $root['url'] );

                if ( ! is_dir( $dir ) ) {
                    continue;
                }

                $normalized[] = array(
                    'tier' => isset( $root['tier'] ) ? sanitize_key( (string) $root['tier'] ) : '',
                    'dir'  => $dir,
                    'url'  => $url,
                );
            }

            return $normalized;
        }

        /**
         * Build one module asset root record from a discovery path.
         *
         * @param string $path Absolute discovery path.
         * @param string $tier Tier slug.
         *
         * @return array<string,string>
         */
        private function build_module_asset_root( $path, $tier ) {
            $path = trailingslashit( (string) $path );
            $tier = sanitize_key( (string) $tier );

            if ( '' === $path || ! is_dir( $path ) ) {
                return array();
            }

            $url = $this->resolve_plugin_relative_url_from_dir( $path );

            if ( '' === $url ) {
                return array();
            }

            return array(
                'tier' => $tier,
                'dir'  => $path,
                'url'  => $url,
            );
        }

        /**
         * Convert a plugin-relative directory path to a plugin URL.
         *
         * @param string $dir Absolute directory path.
         *
         * @return string
         */
        private function resolve_plugin_relative_url_from_dir( $dir ) {
            $dir        = trailingslashit( wp_normalize_path( (string) $dir ) );
            $plugin_dir = trailingslashit( wp_normalize_path( NOESIA_SWA_PLUGIN_DIR ) );

            if ( '' === $dir || '' === $plugin_dir ) {
                return '';
            }

            if ( 0 !== strpos( $dir, $plugin_dir ) ) {
                return '';
            }

            $relative = ltrim( substr( $dir, strlen( $plugin_dir ) ), '/' );

            return trailingslashit( NOESIA_SWA_PLUGIN_URL . str_replace( '\\', '/', $relative ) );
        }

        /**
         * Build the admin asset manifest for folder-based modules.
         *
         * Uses filemtime-based cache busting.
         *
         * @return array<string, array<string, string>>
         */
        private function get_module_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;
                    }

                    $asset_entry = array();
                    $css_file    = trailingslashit( $module_dir ) . 'assets/css/admin.css';
                    $js_file     = trailingslashit( $module_dir ) . 'assets/js/admin.js';

                    if ( file_exists( $css_file ) ) {
                        $asset_entry['css'] = trailingslashit( $asset_root['url'] ) . $module_slug . '/assets/css/admin.css?v=' . rawurlencode( (string) filemtime( $css_file ) );
                    }

                    if ( file_exists( $js_file ) ) {
                        $asset_entry['js'] = trailingslashit( $asset_root['url'] ) . $module_slug . '/assets/js/admin.js?v=' . rawurlencode( (string) filemtime( $js_file ) );
                    }

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

            return $manifest;
        }

        /**
         * Render the settings page.
         *
         * @return void
         */
        public function render_settings_page() {
            require_once NOESIA_SWA_PLUGIN_DIR . 'admin/partials/settings-display.php';
        }

        /**
         * Add the Settings link on the plugins screen.
         *
         * @param array<int,string> $links Existing links.
         * @return array<int,string>
         */
        public function add_plugin_action_links( $links ) {
            $settings_url  = add_query_arg( 'page', 'noesia_swa_settings', admin_url( 'admin.php' ) );
            $settings_link = '<a href="' . esc_url( $settings_url ) . '">' . esc_html__( 'Settings', 'noesia-smart-waiter' ) . '</a>';

            array_unshift( $links, $settings_link );

            return $links;
        }
    }
}
