<?php
/**
 * Free Dashboard module.
 *
 * Strict-isolation entrypoint for the Dashboard admin section.
 * Owns:
 * - section registration
 * - local Dashboard view
 * - shell-manifest lazy loading (no direct admin asset enqueue)
 * - dashboard badge defaults/sanitization
 * - dashboard shared shell labels/context
 * - bootstrap of the module-local Dashboard data service
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_Swa_Module_Dashboard' ) ) {
    /**
     * Free Dashboard module.
     */
    class Noesia_Swa_Module_Dashboard implements Noesia_SWA_Module_Interface {

        /**
         * Unique module identifier.
         *
         * @return string
         */
        public function get_id() {
            return 'dashboard';
        }

        /**
         * Human-readable module label.
         *
         * @return string
         */
        public function get_label() {
            return __( 'Dashboard', 'noesia-smart-waiter' );
        }

        /**
         * Module tier.
         *
         * @return string
         */
        public function get_tier() {
            return 'free';
        }

        /**
         * Whether the module is enabled.
         *
         * @return bool
         */
        public function is_enabled() {
            return (bool) apply_filters( 'noesia_swa_module_dashboard_enabled', true, $this );
        }

        /**
         * Register module hooks.
         *
         * @return void
         */
        public function register_hooks() {
            $this->boot_local_data_service();
            $this->boot_local_news_shorts_service();
            $this->boot_telemetry_service();

            add_action( 'noesia_swa_register_admin_sections', array( $this, 'register_admin_section' ) );
            add_action( 'wp_ajax_noesia_swa_fetch_dashboard_world_data', array( $this, 'handle_fetch_dashboard_world_data' ) );
            add_action( 'wp_ajax_noesia_swa_fetch_dashboard_news_shorts', array( $this, 'handle_fetch_dashboard_news_shorts' ) );
            add_action( 'wp_ajax_noesia_swa_fetch_dashboard_telemetry', array( $this, 'handle_fetch_dashboard_telemetry' ) );

            add_action( 'init', array( $this, 'record_live_session' ), 5 );
            add_action( 'wp_login_failed', array( $this, 'record_failed_login' ) );
            add_action( 'template_redirect', array( $this, 'record_not_found_event' ), 999 );

            add_filter( 'noesia_swa_option_defaults', array( $this, 'filter_option_defaults' ), 10, 3 );
            add_filter( 'noesia_swa_sanitize_options', array( $this, 'sanitize_dashboard_fields' ), 10, 4 );
            add_filter( 'noesia_swa_localized_options', array( $this, 'filter_localized_options' ) );
            add_filter( 'noesia_swa_admin_i18n', array( $this, 'filter_admin_i18n' ), 10, 2 );
            add_filter( 'noesia_swa_shell_active_badge_context', array( $this, 'filter_active_badge_context' ), 10, 3 );
            add_filter( 'noesia_swa_dashboard_recent_activity', array( $this, 'filter_recent_activity' ), 10, 3 );

            do_action( 'noesia_swa_module_dashboard_loaded', $this );
        }

        /**
         * Load the module-local Dashboard data class if it is not already loaded.
         *
         * This is a safe bridge step during extraction:
         * - if the legacy kernel file still loads the class, do nothing
         * - once the kernel stops loading it, the module becomes the owner
         *
         * @return void
         */
        private function boot_local_data_service() {
            if ( class_exists( 'Noesia_SWA_Dashboard_Data' ) ) {
                return;
            }

            $data_file = trailingslashit( __DIR__ ) . 'includes/class-noesia-swa-dashboard-data.php';

            if ( file_exists( $data_file ) ) {
                require_once $data_file;
            }
        }


        /**
         * Load the module-local Dashboard News Shorts service.
         *
         * @return void
         */
        private function boot_local_news_shorts_service() {
            if ( class_exists( 'Noesia_SWA_Dashboard_News_Shorts_Service' ) ) {
                return;
            }

            $service_file = trailingslashit( __DIR__ ) . 'includes/class-noesia-swa-dashboard-news-shorts-service.php';

            if ( file_exists( $service_file ) ) {
                require_once $service_file;
            }
        }


        /**
         * Load the module-local telemetry service if it is not already loaded.
         *
         * @return void
         */
        private function boot_telemetry_service() {
            if ( class_exists( 'Noesia_SWA_Dashboard_Telemetry_Service' ) ) {
                return;
            }

            $service_file = trailingslashit( __DIR__ ) . 'includes/class-noesia-swa-dashboard-telemetry-service.php';

            if ( file_exists( $service_file ) ) {
                require_once $service_file;
            }
        }

        /**
         * Register the Dashboard admin section.
         *
         * @param mixed $registry Admin sections registry.
         * @return void
         */
        public function register_admin_section( $registry ) {
            if ( ! $registry instanceof Noesia_SWA_Admin_Sections ) {
                return;
            }

            $registry->register_section(
                array(
                    'id'          => 'dashboard',
                    'label'       => $this->get_label(),
                    'nav_label'   => $this->get_label(),
                    'nav_sub'     => __( 'General Overview', 'noesia-smart-waiter' ),
                    'icon'        => 'dashicons-chart-pie',
                    'priority'    => 10,
                    'tier'        => 'free',
                    'owner'       => 'dashboard',
                    'hero'        => true,
                    'is_visible'  => true,
                    'nav_visible' => true,
                    'callback'    => array( $this, 'render_admin' ),
                )
            );
        }

        /**
         * Provide module-owned defaults.
         *
         * @param array<string,mixed> $defaults Existing defaults.
         * @param array<string,mixed> $options Existing saved options.
         * @param mixed               $context Caller context.
         * @return array<string,mixed>
         */
        public function filter_option_defaults( $defaults, $options, $context ) {
            if ( ! is_array( $defaults ) ) {
                $defaults = array();
            }

            $defaults['dashboard_stats_source'] = 'requests';
            $defaults['dashboard_world_cities'] = '';

            return $defaults;
        }

        /**
         * Sanitize module-owned settings fields.
         *
         * @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 mixed               $admin Admin instance.
         * @return array<string,mixed>
         */
        public function sanitize_dashboard_fields( $new_input, $input, $existing, $admin ) {
            if ( ! is_array( $new_input ) ) {
                $new_input = array();
            }

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

            if ( isset( $input['dashboard_stats_source'] ) ) {
                $source = sanitize_key( wp_unslash( (string) $input['dashboard_stats_source'] ) );
                $allowed = array( 'requests', 'reminders', 'appointments' );

                if ( ! in_array( $source, $allowed, true ) ) {
                    $source = 'requests';
                }

                $new_input['dashboard_stats_source'] = $source;
            }

            if ( isset( $input['dashboard_world_cities'] ) ) {
                $raw_lines       = preg_split( '/\r\n|\r|\n/', sanitize_textarea_field( wp_unslash( (string) $input['dashboard_world_cities'] ) ) );
                $sanitized_lines = array();
                $max_items       = 4;

                if ( is_array( $raw_lines ) ) {
                    foreach ( $raw_lines as $raw_line ) {
                        $line    = trim( (string) $raw_line );
                        $city    = '';
                        $country = '';

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

                        if ( false !== strpos( $line, '|' ) ) {
                            $parts   = array_map( 'trim', explode( '|', $line ) );
                            $city    = isset( $parts[0] ) ? sanitize_text_field( (string) $parts[0] ) : '';
                            $country = isset( $parts[1] ) ? strtoupper( preg_replace( '/[^A-Z]/', '', sanitize_text_field( (string) $parts[1] ) ) ) : '';
                        } else {
                            $parts   = array_map( 'trim', explode( ',', $line ) );
                            $city    = isset( $parts[0] ) ? sanitize_text_field( (string) $parts[0] ) : '';
                            $country = isset( $parts[1] ) ? strtoupper( preg_replace( '/[^A-Z]/', '', sanitize_text_field( (string) $parts[1] ) ) ) : '';
                        }

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

                        $country = substr( (string) $country, 0, 2 );

                        $sanitized_lines[] = '' !== $country
                            ? $city . ', ' . $country
                            : $city;

                        if ( count( $sanitized_lines ) >= $max_items ) {
                            break;
                        }
                    }
                }

                $new_input['dashboard_world_cities'] = implode( "\n", $sanitized_lines );
            }

            return $new_input;
        }

        /**
         * Fetch the live Feel the World payload over AJAX so the Dashboard can
         * paint fast and hydrate heavy world data after the initial render.
         *
         * @return void
         */
        /**
         * Fetch one lazy-loaded News Shorts slice over AJAX.
         *
         * @return void
         */
        public function handle_fetch_dashboard_news_shorts() {
            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
                );
            }

            $service = $this->get_dashboard_news_shorts_service();

            if ( ! $service instanceof Noesia_SWA_Dashboard_News_Shorts_Service ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'Dashboard News Shorts service is unavailable.', 'noesia-smart-waiter' ),
                    ),
                    500
                );
            }

            $options  = get_option( 'noesia_swa_options', array() );
            $options  = is_array( $options ) ? $options : array();
            $category = isset( $_POST['category'] ) ? sanitize_key( wp_unslash( (string) $_POST['category'] ) ) : 'top';
            $offset   = isset( $_POST['offset'] ) ? absint( wp_unslash( (string) $_POST['offset'] ) ) : 0;
            $limit    = isset( $_POST['limit'] ) ? absint( wp_unslash( (string) $_POST['limit'] ) ) : 12;

            wp_send_json_success( $service->get_feed_slice( $options, $category, $offset, $limit ) );
        }

        /**
         * Fetch the live matrix telemetry payload over AJAX.
         *
         * @return void
         */
        public function handle_fetch_dashboard_telemetry() {
            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
                );
            }

            $telemetry = $this->get_dashboard_telemetry_service();

            if ( ! $telemetry instanceof Noesia_SWA_Dashboard_Telemetry_Service ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'Dashboard telemetry service is unavailable.', 'noesia-smart-waiter' ),
                    ),
                    500
                );
            }

            wp_send_json_success( $telemetry->get_snapshot() );
        }

        /**
         * Capture an approximate live session heartbeat for dashboard telemetry.
         *
         * @return void
         */
        public function record_live_session() {
            $telemetry = $this->get_dashboard_telemetry_service();

            if ( $telemetry instanceof Noesia_SWA_Dashboard_Telemetry_Service ) {
                $telemetry->record_active_session();
            }
        }

        /**
         * Capture failed login attempts for dashboard telemetry.
         *
         * @param string $username Submitted username.
         * @return void
         */
        public function record_failed_login( $username ) {
            $telemetry = $this->get_dashboard_telemetry_service();

            if ( $telemetry instanceof Noesia_SWA_Dashboard_Telemetry_Service ) {
                $telemetry->record_failed_login( (string) $username );
            }
        }

        /**
         * Capture recent 404 requests for dashboard telemetry.
         *
         * @return void
         */
        public function record_not_found_event() {
            if ( ! is_404() ) {
                return;
            }

            $telemetry = $this->get_dashboard_telemetry_service();

            if ( $telemetry instanceof Noesia_SWA_Dashboard_Telemetry_Service ) {
                $telemetry->record_not_found_event();
            }
        }

        public function handle_fetch_dashboard_world_data() {
            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
                );
            }

            $options = get_option( 'noesia_swa_options', array() );
            $options = is_array( $options ) ? $options : array();
            $data    = $this->get_dashboard_data_service();

            if ( ! $data instanceof Noesia_SWA_Dashboard_Data ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'Dashboard data service is unavailable.', 'noesia-smart-waiter' ),
                    ),
                    500
                );
            }

            $focus = isset( $_POST['world_focus'] ) ? sanitize_key( wp_unslash( (string) $_POST['world_focus'] ) ) : 'country';
            $query = isset( $_POST['world_query'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['world_query'] ) ) : '';

            wp_send_json_success(
                $data->build_world_live_payload(
                    $options,
                    array(
                        'focus' => $focus,
                        'query' => $query,
                    )
                )
            );
        }

        /**
         * Resolve the Dashboard News Shorts service instance.
         *
         * @return Noesia_SWA_Dashboard_News_Shorts_Service|null
         */
        private function get_dashboard_news_shorts_service() {
            if ( class_exists( 'Noesia_SWA_Dashboard_News_Shorts_Service' ) ) {
                return new Noesia_SWA_Dashboard_News_Shorts_Service();
            }

            return null;
        }

        /**
         * Provide public News Shorts category labels to the local view.
         *
         * @return array<string,array<string,string>>
         */
        public function get_dashboard_news_shorts_categories() {
            $service = $this->get_dashboard_news_shorts_service();
            $options = get_option( 'noesia_swa_options', array() );
            $options = is_array( $options ) ? $options : array();

            if ( $service instanceof Noesia_SWA_Dashboard_News_Shorts_Service ) {
                return $service->get_categories( $options );
            }

            return array();
        }

        /**
         * Resolve the module-owned Dashboard data service instance.
         *
         * @return Noesia_SWA_Dashboard_Data|null
         */
        /**
         * Resolve the telemetry service instance.
         *
         * @return Noesia_SWA_Dashboard_Telemetry_Service|null
         */
        private function get_dashboard_telemetry_service() {
            if ( class_exists( 'Noesia_SWA_Dashboard_Telemetry_Service' ) ) {
                return new Noesia_SWA_Dashboard_Telemetry_Service();
            }

            return null;
        }

        private function get_dashboard_data_service() {
            if ( function_exists( 'noesia_swa' ) ) {
                $plugin = noesia_swa();

                if ( is_object( $plugin ) && isset( $plugin->dashboard_data ) && $plugin->dashboard_data instanceof Noesia_SWA_Dashboard_Data ) {
                    return $plugin->dashboard_data;
                }
            }

            if ( class_exists( 'Noesia_SWA_Dashboard_Data' ) ) {
                return new Noesia_SWA_Dashboard_Data();
            }

            return null;
        }

        /**
         * Ensure shared localized options contain Dashboard-owned fields.
         *
         * Also provides legacy fallback from global_active_badge_type.
         *
         * @param array<string,mixed> $options Localized options payload.
         * @return array<string,mixed>
         */
        public function filter_localized_options( $options ) {
            if ( ! is_array( $options ) ) {
                $options = array();
            }

            if ( empty( $options['dashboard_stats_source'] ) && ! empty( $options['global_active_badge_type'] ) ) {
                $legacy_source = sanitize_key( (string) $options['global_active_badge_type'] );

                if ( in_array( $legacy_source, array( 'requests', 'reminders', 'appointments' ), true ) ) {
                    $options['dashboard_stats_source'] = $legacy_source;
                }
            }

            if ( empty( $options['dashboard_stats_source'] ) ) {
                $defaults = $this->filter_option_defaults( array(), $options, $this );
                $options['dashboard_stats_source'] = isset( $defaults['dashboard_stats_source'] ) ? $defaults['dashboard_stats_source'] : 'requests';
            }

            if ( ! array_key_exists( 'dashboard_world_cities', $options ) ) {
                $defaults = $this->filter_option_defaults( array(), $options, $this );
                $options['dashboard_world_cities'] = isset( $defaults['dashboard_world_cities'] ) ? $defaults['dashboard_world_cities'] : '';
            }

            return $options;
        }

        /**
         * Provide Dashboard-owned shared shell i18n.
         *
         * @param array<string,string> $i18n Existing i18n payload.
         * @param mixed                $admin Admin instance.
         * @return array<string,string>
         */
        public function filter_admin_i18n( $i18n, $admin ) {
            if ( ! is_array( $i18n ) ) {
                $i18n = array();
            }

            $i18n['section_dashboard']          = __( 'Dashboard', 'noesia-smart-waiter' );
            $i18n['briefing_open_appointments'] = __( 'Open Appointments', 'noesia-smart-waiter' );
            $i18n['badge_appointments_label']   = __( 'Appointments', 'noesia-smart-waiter' );
            $i18n['badge_appointment_singular'] = __( 'Appointment', 'noesia-smart-waiter' );

            return $i18n;
        }

        /**
         * Provide the live active badge context for the shared shell.
         *
         * @param array<string,mixed> $context Default shell context.
         * @param array<string,mixed> $options Saved options.
         * @param mixed               $helper Shell data helper.
         * @return array<string,mixed>
         */
        public function filter_active_badge_context( $context, $options, $helper ) {
            if ( ! is_array( $context ) ) {
                $context = array();
            }

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

            $source = '';

            if ( ! empty( $options['dashboard_stats_source'] ) ) {
                $source = sanitize_key( (string) $options['dashboard_stats_source'] );
            } elseif ( ! empty( $options['global_active_badge_type'] ) ) {
                $source = sanitize_key( (string) $options['global_active_badge_type'] );
            }

            if ( ! in_array( $source, array( 'requests', 'reminders', 'appointments' ), true ) ) {
                $source = 'requests';
            }

            if ( 'appointments' === $source ) {
                $count = $this->get_today_appointments_count();

                return array_merge(
                    $context,
                    array(
                        'type'    => 'appointments',
                        'visible' => $count > 0,
                        'count'   => $count,
                        'label'   => ( 1 === $count ) ? __( 'Appointment', 'noesia-smart-waiter' ) : __( 'Appointments', 'noesia-smart-waiter' ),
                        'text'    => ( 1 === $count ) ? __( 'Appointment', 'noesia-smart-waiter' ) : __( 'Appointments', 'noesia-smart-waiter' ),
                        'target'  => 'appointments',
                    )
                );
            }

            return $context;
        }

        /**
         * Provide honest recent workspace activity from the latest Noesia-owned
         * content updates instead of hardcoded placeholder entries.
         *
         * @param array<int,array<string,string>> $items Existing items.
         * @param array<string,mixed>             $context Dashboard context.
         * @param array<string,mixed>             $options Saved options.
         * @return array<int,array<string,string>>
         */
        public function filter_recent_activity( $items, $context, $options ) {
            if ( ! is_array( $items ) ) {
                $items = array();
            }

            $tracked_post_types = $this->get_recent_activity_post_types();

            if ( empty( $tracked_post_types ) ) {
                return $items;
            }

            $query = new WP_Query(
                array(
                    'post_type'              => $tracked_post_types,
                    'post_status'            => array( 'publish', 'private', 'draft', 'pending', 'future' ),
                    'posts_per_page'         => 15,
                    'orderby'                => 'modified',
                    'order'                  => 'DESC',
                    'ignore_sticky_posts'    => true,
                    'no_found_rows'          => true,
                    'update_post_meta_cache' => false,
                    'update_post_term_cache' => false,
                )
            );

            if ( ! $query->have_posts() ) {
                wp_reset_postdata();
                return $items;
            }

            $activity = array();

            foreach ( $query->posts as $post ) {
                if ( ! $post instanceof WP_Post ) {
                    continue;
                }

                $module_label = $this->get_recent_activity_post_type_label( $post->post_type );
                $modified_ts  = get_post_modified_time( 'U', false, $post );
                $timestamp    = $modified_ts ? absint( $modified_ts ) : current_time( 'timestamp' );
                $time_label   = '[' . wp_date( 'H:i', $timestamp ) . '] ' . $module_label;
                $title        = get_the_title( $post );

                if ( '' !== $title ) {
                    $text = sprintf(
                        /* translators: 1: content type label, 2: content title */
                        __( 'Updated %1$s: %2$s.', 'noesia-smart-waiter' ),
                        $module_label,
                        sanitize_text_field( $title )
                    );
                } else {
                    $text = sprintf(
                        /* translators: 1: content type label, 2: post id */
                        __( 'Updated %1$s #%2$d.', 'noesia-smart-waiter' ),
                        $module_label,
                        (int) $post->ID
                    );
                }

                $activity[] = array(
                    'label'     => $time_label,
                    'text'      => $text,
                    'timestamp' => $timestamp,
                    'source'    => sanitize_key( (string) $post->post_type ),
                );
            }

            wp_reset_postdata();

            return ! empty( $activity ) ? array_merge( $items, $activity ) : $items;
        }

        /**
         * Resolve registered post types that belong to Noesia-owned activity.
         *
         * @return array<int,string>
         */
        private function get_recent_activity_post_types() {
            $tracked_post_types = array();
            $post_type_objects  = get_post_types( array(), 'objects' );

            if ( is_array( $post_type_objects ) ) {
                foreach ( $post_type_objects as $post_type => $object ) {
                    $post_type = sanitize_key( (string) $post_type );

                    if ( $this->is_recent_activity_post_type( $post_type, $object ) ) {
                        $tracked_post_types[] = $post_type;
                    }
                }
            }

            /**
             * Allow modules to opt their own registered post types into the
             * Dashboard recent activity feed without central Dashboard edits.
             *
             * @param array<int,string>             $tracked_post_types Post types.
             * @param array<string,WP_Post_Type>    $post_type_objects  Registered post type objects.
             * @param Noesia_Swa_Module_Dashboard   $this               Dashboard module.
             */
            $tracked_post_types = apply_filters( 'noesia_swa_dashboard_recent_activity_post_types', $tracked_post_types, $post_type_objects, $this );

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

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

        /**
         * Check whether one post type should feed generic recent activity.
         *
         * @param string       $post_type Post type key.
         * @param WP_Post_Type $object    Post type object.
         * @return bool
         */
        private function is_recent_activity_post_type( $post_type, $object ) {
            if ( '' === $post_type ) {
                return false;
            }

            if ( 0 === strpos( $post_type, 'noesia_' ) || 0 === strpos( $post_type, 'swa_' ) || 0 === strpos( $post_type, 'safelog_' ) ) {
                return true;
            }

            if ( 'shop_order' === $post_type ) {
                return true;
            }

            return ( $object instanceof WP_Post_Type && ! empty( $object->noesia_swa_recent_activity ) );
        }

        /**
         * Get a safe label for a recent activity post type.
         *
         * @param string $post_type Post type key.
         * @return string
         */
        private function get_recent_activity_post_type_label( $post_type ) {
            $post_type_object = get_post_type_object( $post_type );

            if ( $post_type_object && ! empty( $post_type_object->labels->singular_name ) ) {
                return sanitize_text_field( (string) $post_type_object->labels->singular_name );
            }

            if ( $post_type_object && ! empty( $post_type_object->labels->name ) ) {
                return sanitize_text_field( (string) $post_type_object->labels->name );
            }

            return sanitize_text_field( ucwords( str_replace( '_', ' ', (string) $post_type ) ) );
        }

        /**
         * Count today's appointments for the shell badge.
         *
         * @return int
         */
        private function get_today_appointments_count() {
            $today = current_time( 'Y-m-d' );

            $query = new WP_Query(
                array(
                    'post_type'      => 'noesia_appointment',
                    'post_status'    => array( 'publish', 'private' ),
                    'posts_per_page' => -1,
                    'fields'         => 'ids',
                    // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Dashboard badge count filters appointments by stored appointment_date meta.
                    'meta_query'     => array(
                        array(
                            'key'   => 'appointment_date',
                            'value' => $today,
                        ),
                    ),
                )
            );

            return absint( $query->found_posts );
        }

        /**
         * Render the module-local Dashboard admin view.
         *
         * @return void
         */
        public function render_admin() {
            $module_view = trailingslashit( __DIR__ ) . 'views/admin.php';
            $legacy_view = NOESIA_SWA_PLUGIN_DIR . 'admin/partials/sections/dashboard.php';

            do_action( 'noesia_swa_before_render_dashboard_admin', $this );

            if ( file_exists( $module_view ) ) {
                include $module_view;
                do_action( 'noesia_swa_after_render_dashboard_admin', $this );
                return;
            }

            if ( file_exists( $legacy_view ) ) {
                include $legacy_view;
                do_action( 'noesia_swa_after_render_dashboard_admin', $this );
                return;
            }

            echo '<div class="noesia-swa-card noesia-swa-card--span2">';
            echo '<div class="noesia-swa-card-header"><div>';
            echo '<h3>' . esc_html__( 'Dashboard', 'noesia-smart-waiter' ) . '</h3>';
            echo '<p class="noesia-swa-muted">' . esc_html__( 'Module view missing.', 'noesia-smart-waiter' ) . '</p>';
            echo '</div></div></div>';

            do_action( 'noesia_swa_after_render_dashboard_admin', $this );
        }
    }
}
