<?php
/**
 * Global Data Hub service.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_SWA_Global_Data_Hub_Service' ) ) {
    /**
     * Strict Eurostat tourism service.
     */
    class Noesia_SWA_Global_Data_Hub_Service {

        /**
         * Settings option key.
         */
        const OPTION_KEY = 'noesia_swa_global_data_hub_settings';

        /**
         * Cache key prefix.
         */
        const CACHE_PREFIX = 'noesia_swa_gdh_strict_';

        /**
         * Get default settings.
         *
         * @return array<string,mixed>
         */
        public function get_default_settings() {
            return array(
                'country_code'  => 'EL',
                'refresh_hours' => 12,
            );
        }

        /**
         * Get saved settings.
         *
         * @return array<string,mixed>
         */
        public function get_settings() {
            $saved = get_option( self::OPTION_KEY, array() );
            $saved = is_array( $saved ) ? $saved : array();
            return wp_parse_args( $saved, $this->get_default_settings() );
        }

        /**
         * Save settings.
         *
         * @param array<string,mixed> $input Raw settings.
         * @return array<string,mixed>
         */
        public function save_settings( $input ) {
            $defaults = $this->get_default_settings();
            $old      = $this->get_settings();
            $allowed  = $this->get_country_options();

            $country_code = isset( $input['country_code'] ) ? $this->normalize_country_code( (string) $input['country_code'] ) : $defaults['country_code'];
            if ( ! isset( $allowed[ $country_code ] ) ) {
                $country_code = $defaults['country_code'];
            }

            $refresh_hours = isset( $input['refresh_hours'] ) ? max( 1, min( 72, absint( $input['refresh_hours'] ) ) ) : (int) $defaults['refresh_hours'];

            $settings = array(
                'country_code'  => $country_code,
                'refresh_hours' => $refresh_hours,
            );

            update_option( self::OPTION_KEY, $settings, false );
            $this->clear_country_cache( isset( $old['country_code'] ) ? (string) $old['country_code'] : '' );
            $this->clear_country_cache( $country_code );

            return $settings;
        }

        /**
         * Get public settings.
         *
         * @return array<string,mixed>
         */
        public function get_public_settings() {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( isset( $settings['country_code'] ) ? (string) $settings['country_code'] : 'EL' );

            return array(
                'country_code'  => $country_code,
                'country_label' => isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code,
                'refresh_hours' => absint( $settings['refresh_hours'] ),
            );
        }

        /**
         * Supported Eurostat country options.
         *
         * @return array<string,string>
         */
        public function get_country_options() {
            return array(
                'AL' => 'Albania',
                'AT' => 'Austria',
                'BE' => 'Belgium',
                'BG' => 'Bulgaria',
                'BA' => 'Bosnia and Herzegovina',
                'CH' => 'Switzerland',
                'CY' => 'Cyprus',
                'CZ' => 'Czechia',
                'DE' => 'Germany',
                'DK' => 'Denmark',
                'EE' => 'Estonia',
                'EL' => 'Greece',
                'ES' => 'Spain',
                'FI' => 'Finland',
                'FR' => 'France',
                'HR' => 'Croatia',
                'HU' => 'Hungary',
                'IE' => 'Ireland',
                'IS' => 'Iceland',
                'IT' => 'Italy',
                'LI' => 'Liechtenstein',
                'LT' => 'Lithuania',
                'LU' => 'Luxembourg',
                'LV' => 'Latvia',
                'ME' => 'Montenegro',
                'MK' => 'North Macedonia',
                'MT' => 'Malta',
                'NL' => 'Netherlands',
                'NO' => 'Norway',
                'PL' => 'Poland',
                'PT' => 'Portugal',
                'RO' => 'Romania',
                'RS' => 'Serbia',
                'SE' => 'Sweden',
                'SI' => 'Slovenia',
                'SK' => 'Slovakia',
                'TR' => 'Türkiye',
            );
        }

        /**
         * Get default compare countries.
         *
         * @return array<int,string>
         */
        public function get_compare_default_country_codes() {
            $settings        = $this->get_settings();
            $current_country = $this->normalize_country_code( isset( $settings['country_code'] ) ? (string) $settings['country_code'] : 'EL' );
            $defaults        = array( $current_country, 'DE', 'FR', 'IT', 'ES', 'PT' );
            return array_slice( $this->sanitize_compare_country_codes( $defaults ), 0, 4 );
        }

        /**
         * Get shell payload.
         *
         * @return array<string,mixed>
         */
        public function get_shell_payload() {
            $settings  = $this->get_settings();
            $countries = $this->get_country_options();
            $country   = isset( $countries[ $settings['country_code'] ] ) ? $countries[ $settings['country_code'] ] : $settings['country_code'];

            return array(
                'settings' => $settings,
                'overview' => array(
                    array(
                        'label' => __( 'Country Scope', 'noesia-smart-waiter' ),
                        'value' => $country,
                        'meta'  => __( 'Eurostat supported country', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Source Stack', 'noesia-smart-waiter' ),
                        'value' => 'Eurostat + World Bank + IMF + OECD + WHO + PubMed + ClinicalTrials',
                        'meta'  => __( 'Strict official APIs', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Refresh Window', 'noesia-smart-waiter' ),
                        'value' => absint( $settings['refresh_hours'] ) . 'h',
                        'meta'  => __( 'Transient cache', 'noesia-smart-waiter' ),
                    ),
                ),
                'note' => __( 'Strict mode uses only explicit official datasets per provider. Tourism stays on Eurostat, Development stays on World Bank WDI, Digital / Connectivity stays on fixed World Bank WDI ICT indicators, Population & Society stays on fixed World Bank demographic and social indicators, IMF Macro stays on fixed IMF DataMapper WEO indicators, OECD stays on fixed labour/activity selections, Health stays on exact WHO GHO indicators, Medical Evidence stays on PubMed literature search plus ClinicalTrials.gov registry search, and Compare Countries reuses only a fixed comparison deck from already-enabled official metrics.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Get tourism payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_tourism_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;

            if ( $force ) {
                $this->clear_country_cache( $country_code );
            }

            $arrivals = $this->fetch_strict_dataset(
                array(
                    'cache_key'    => 'arrivals',
                    'dataset_code' => 'tour_occ_arm',
                    'title'        => __( 'Arrivals at accommodation establishments', 'noesia-smart-waiter' ),
                    'definition'   => __( 'Official monthly arrivals at tourist accommodation establishments. This metric must not be interpreted as border arrivals.', 'noesia-smart-waiter' ),
                    'mode'         => 'number',
                    'last_periods' => 18,
                    'selectors'    => array(
                        'freq'    => array( 'codes' => array( 'M' ) ),
                        'unit'    => array( 'codes' => array( 'NR' ) ),
                        'nace_r2' => array( 'codes' => array( 'I551-I553' ) ),
                    ),
                    'allow_series_dimensions' => array( 'c_resid' ),
                    'series_dimension_rules'  => array(
                        'c_resid' => array(
                            'preferred_codes' => array( 'TOTAL', 'DOM', 'FOR' ),
                            'labels'          => array(
                                'TOTAL' => __( 'Total arrivals', 'noesia-smart-waiter' ),
                                'DOM'   => __( 'Domestic arrivals', 'noesia-smart-waiter' ),
                                'FOR'   => __( 'Foreign arrivals', 'noesia-smart-waiter' ),
                            ),
                        ),
                    ),
                ),
                $country_code,
                $country_name,
                absint( $settings['refresh_hours'] )
            );

            $nights = $this->fetch_strict_dataset(
                array(
                    'cache_key'    => 'nights',
                    'dataset_code' => 'tour_occ_nim',
                    'title'        => __( 'Nights spent at accommodation establishments', 'noesia-smart-waiter' ),
                    'definition'   => __( 'Official monthly nights spent by guests at tourist accommodation establishments.', 'noesia-smart-waiter' ),
                    'mode'         => 'number',
                    'last_periods' => 18,
                    'selectors'    => array(
                        'freq'    => array( 'codes' => array( 'M' ) ),
                        'unit'    => array( 'codes' => array( 'NR' ) ),
                        'nace_r2' => array( 'codes' => array( 'I551-I553' ) ),
                    ),
                    'allow_series_dimensions' => array( 'c_resid' ),
                    'series_dimension_rules'  => array(
                        'c_resid' => array(
                            'preferred_codes' => array( 'TOTAL', 'DOM', 'FOR' ),
                            'labels'          => array(
                                'TOTAL' => __( 'Total nights', 'noesia-smart-waiter' ),
                                'DOM'   => __( 'Domestic nights', 'noesia-smart-waiter' ),
                                'FOR'   => __( 'Foreign nights', 'noesia-smart-waiter' ),
                            ),
                        ),
                    ),
                ),
                $country_code,
                $country_name,
                absint( $settings['refresh_hours'] )
            );

            $occupancy = $this->fetch_strict_dataset(
                array(
                    'cache_key'    => 'occupancy',
                    'dataset_code' => 'tour_occ_mnor',
                    'title'        => __( 'Hotel occupancy rate', 'noesia-smart-waiter' ),
                    'definition'   => __( 'Official monthly occupancy rate for hotels and similar accommodation only. Bed-places and bedrooms are shown separately when the official dataset provides both.', 'noesia-smart-waiter' ),
                    'mode'         => 'percentage',
                    'last_periods' => 18,
                    'selectors'    => array(
                        'freq'    => array( 'codes' => array( 'M' ) ),
                        'nace_r2' => array( 'codes' => array( 'I551' ) ),
                    ),
                    'allow_series_dimensions' => array( 'unit', 'accom', 'accomunit', 'bedroom', 'bedplace', 'bedroom_bedplace' ),
                    'series_dimension_rules'  => array(
                        'unit' => array(
                            'label_patterns' => array( 'bed', 'bedroom', 'room', 'place' ),
                        ),
                        'accomunit' => array(
                            'label_patterns' => array( 'bed', 'bedroom', 'room', 'place' ),
                            'labels'         => array(
                                'BED-PL'  => __( 'Bed-place occupancy', 'noesia-smart-waiter' ),
                                'BEDROOM' => __( 'Bedroom occupancy', 'noesia-smart-waiter' ),
                            ),
                        ),
                    ),
                ),
                $country_code,
                $country_name,
                absint( $settings['refresh_hours'] )
            );

            return array(
                'overview_cards' => $this->build_overview_cards( $country_name, $settings, $arrivals, $nights, $occupancy ),
                'note'           => __( 'Tourism data is loaded only from official Eurostat accommodation datasets. Arrivals and nights are shown by guest origin when the official series is available; hotel occupancy stays separate and is never mixed with accommodation arrivals.', 'noesia-smart-waiter' ),
                'datasets'       => array(
                    'arrivals'  => $arrivals,
                    'nights'    => $nights,
                    'occupancy' => $occupancy,
                ),
            );
        
        }

        /**
         * Build overview cards.
         *
         * @param string              $country_name Country label.
         * @param array<string,mixed> $settings Settings.
         * @param array<string,mixed> $arrivals Arrivals dataset.
         * @param array<string,mixed> $nights Nights dataset.
         * @param array<string,mixed> $occupancy Occupancy dataset.
         * @return array<int,array<string,string>>
         */
        private function build_overview_cards( $country_name, $settings, $arrivals, $nights, $occupancy ) {
            $cards = array(
                array(
                    'label' => __( 'Country Scope', 'noesia-smart-waiter' ),
                    'value' => $country_name,
                    'meta'  => __( 'Eurostat supported country', 'noesia-smart-waiter' ),
                ),
                array(
                    'label' => __( 'Official Source', 'noesia-smart-waiter' ),
                    'value' => 'Eurostat',
                    'meta'  => __( 'Accommodation statistics API', 'noesia-smart-waiter' ),
                ),
                array(
                    'label' => __( 'Refresh Window', 'noesia-smart-waiter' ),
                    'value' => absint( $settings['refresh_hours'] ) . 'h',
                    'meta'  => __( 'Transient cache', 'noesia-smart-waiter' ),
                ),
            );

            foreach ( array( $arrivals, $nights, $occupancy ) as $dataset ) {
                $latest = $this->select_primary_series_latest( $dataset );
                if ( empty( $latest['formatted'] ) ) {
                    continue;
                }
                $cards[] = array(
                    'label' => isset( $dataset['title'] ) ? (string) $dataset['title'] : __( 'Official Statistic', 'noesia-smart-waiter' ),
                    'value' => isset( $latest['formatted'] ) ? (string) $latest['formatted'] : '—',
                    'meta'  => isset( $latest['period_label'] ) ? (string) $latest['period_label'] : '',
                );
            }

            return $cards;
        
        }

        /**
         * Fetch one strict dataset.
         *
         * @param array<string,mixed> $config Dataset configuration.
         * @param string              $country_code Country code.
         * @param string              $country_name Country name.
         * @param int                 $refresh_hours Refresh hours.
         * @return array<string,mixed>
         */
        private function fetch_strict_dataset( $config, $country_code, $country_name, $refresh_hours ) {
            $cache_key = self::CACHE_PREFIX . sanitize_key( (string) $config['cache_key'] ) . '_' . strtolower( sanitize_key( $country_code ) );
            $cached    = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $request = $this->request_eurostat_dataset(
                (string) $config['dataset_code'],
                $country_code,
                isset( $config['last_periods'] ) ? absint( $config['last_periods'] ) : 12
            );

            if ( 'ok' !== $request['status'] ) {
                $payload = $this->build_unavailable_payload( $config, $country_name, isset( $request['message'] ) ? (string) $request['message'] : __( 'Official data request failed.', 'noesia-smart-waiter' ) );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $payload = $this->build_dataset_payload_from_jsonstat( (array) $request['data'], $config, $country_code, $country_name );
            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, $refresh_hours ) );
            return $payload;
        }

        /**
         * Request one Eurostat dataset.
         *
         * @param string $dataset_code Dataset code.
         * @param string $country_code Country code.
         * @param int    $last_periods Last periods.
         * @return array<string,mixed>
         */
        private function request_eurostat_dataset( $dataset_code, $country_code, $last_periods ) {
            $url = add_query_arg(
                array(
                    'lang'           => 'EN',
                    'format'         => 'JSON',
                    'geo'            => strtoupper( sanitize_text_field( $country_code ) ),
                    'lastTimePeriod' => max( 1, absint( $last_periods ) ),
                ),
                'https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/' . rawurlencode( sanitize_key( $dataset_code ) )
            );

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 15,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'ec.europa.eu', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            $data = json_decode( $body, true );

            if ( $code >= 400 ) {
                $message = __( 'Eurostat returned an HTTP error.', 'noesia-smart-waiter' );
                if ( is_array( $data ) && ! empty( $data['error']['label'] ) ) {
                    $message = sanitize_text_field( (string) $data['error']['label'] );
                }
                return array(
                    'status'  => 'unavailable',
                    'message' => $message,
                );
            }

            if ( ! is_array( $data ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'Eurostat returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            if ( ! empty( $data['error']['label'] ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => sanitize_text_field( (string) $data['error']['label'] ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data,
            );
        }

        /**
         * Build one dataset payload from JSON-stat.
         *
         * @param array<string,mixed> $data Dataset data.
         * @param array<string,mixed> $config Dataset config.
         * @param string              $country_code Country code.
         * @param string              $country_name Country name.
         * @return array<string,mixed>
         */
        private function build_dataset_payload_from_jsonstat( $data, $config, $country_code, $country_name ) {
            $parsed = $this->parse_jsonstat_dataset( $data );
            if ( empty( $parsed['dimensions'] ) || empty( $parsed['sizes'] ) ) {
                return $this->build_unavailable_payload( $config, $country_name, __( 'Eurostat returned a dataset without readable dimensions.', 'noesia-smart-waiter' ) );
            }

            $selection = $this->resolve_strict_selection( $parsed, $config, $country_code );
            if ( 'ok' !== $selection['status'] ) {
                return $this->build_unavailable_payload( $config, $country_name, isset( $selection['message'] ) ? (string) $selection['message'] : __( 'No strict official series could be resolved.', 'noesia-smart-waiter' ) );
            }

            $series_payloads = array();
            foreach ( $selection['series'] as $series_config ) {
                $rows = $this->extract_series_rows( $parsed, $series_config, (string) $config['mode'] );
                if ( empty( $rows ) ) {
                    continue;
                }

                $series_payloads[] = array(
                    'label'      => isset( $series_config['label'] ) ? (string) $series_config['label'] : __( 'Official series', 'noesia-smart-waiter' ),
                    'selector'   => isset( $series_config['selector'] ) ? (string) $series_config['selector'] : '',
                    'latest'     => $this->build_latest_point( $rows, (string) $config['mode'] ),
                    'rows'       => $rows,
                    'quarters'   => $this->aggregate_rows_to_quarters( $rows, (string) $config['mode'] ),
                );
            }

            if ( empty( $series_payloads ) ) {
                return $this->build_unavailable_payload( $config, $country_name, __( 'Eurostat did not return a usable official series for this country and dataset.', 'noesia-smart-waiter' ) );
            }

            return array(
                'status'       => 'ok',
                'title'        => (string) $config['title'],
                'definition'   => (string) $config['definition'],
                'dataset_code' => (string) $config['dataset_code'],
                'source'       => 'Eurostat Statistics API',
                'scope'        => $country_name,
                'mode'         => (string) $config['mode'],
                'unit_label'   => isset( $selection['unit_label'] ) ? (string) $selection['unit_label'] : '',
                'updated'      => isset( $data['updated'] ) ? sanitize_text_field( (string) $data['updated'] ) : '',
                'series'       => $series_payloads,
                'notice'       => __( 'Only official Eurostat values are shown. Unsupported or ambiguous series stay unavailable instead of being guessed.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Build unavailable payload.
         *
         * @param array<string,mixed> $config Dataset config.
         * @param string              $country_name Country name.
         * @param string              $message Message.
         * @return array<string,mixed>
         */
        private function build_unavailable_payload( $config, $country_name, $message ) {
            return array(
                'status'       => 'unavailable',
                'title'        => (string) $config['title'],
                'definition'   => (string) $config['definition'],
                'dataset_code' => (string) $config['dataset_code'],
                'source'       => 'Eurostat Statistics API',
                'scope'        => $country_name,
                'mode'         => (string) $config['mode'],
                'unit_label'   => '',
                'updated'      => '',
                'series'       => array(),
                'notice'       => $message,
            );
        }

        /**
         * Parse JSON-stat dimensions.
         *
         * @param array<string,mixed> $data Dataset.
         * @return array<string,mixed>
         */
        private function parse_jsonstat_dataset( $data ) {
            $ids        = isset( $data['id'] ) && is_array( $data['id'] ) ? array_values( $data['id'] ) : array();
            $sizes      = isset( $data['size'] ) && is_array( $data['size'] ) ? array_values( $data['size'] ) : array();
            $dimensions = isset( $data['dimension'] ) && is_array( $data['dimension'] ) ? $data['dimension'] : array();
            $values     = isset( $data['value'] ) ? $data['value'] : array();
            $dim_map    = array();
            $time_dim   = -1;

            foreach ( $ids as $index => $dim_id ) {
                $dim_id = (string) $dim_id;
                if ( empty( $dimensions[ $dim_id ]['category'] ) || ! is_array( $dimensions[ $dim_id ]['category'] ) ) {
                    continue;
                }
                $category = $dimensions[ $dim_id ]['category'];
                $codes    = array();
                $labels   = array();

                if ( isset( $category['index'] ) && is_array( $category['index'] ) ) {
                    foreach ( $category['index'] as $code => $position ) {
                        if ( is_numeric( $position ) ) {
                            $codes[ (int) $position ] = (string) $code;
                        }
                    }
                    ksort( $codes );
                }

                if ( isset( $category['label'] ) && is_array( $category['label'] ) ) {
                    foreach ( $category['label'] as $code => $label ) {
                        $labels[ (string) $code ] = sanitize_text_field( (string) $label );
                    }
                }

                $dim_map[ $index ] = array(
                    'id'     => $dim_id,
                    'size'   => isset( $sizes[ $index ] ) ? (int) $sizes[ $index ] : 0,
                    'codes'  => $codes,
                    'labels' => $labels,
                );

                if ( false !== strpos( strtolower( $dim_id ), 'time' ) ) {
                    $time_dim = (int) $index;
                }
            }

            return array(
                'dimensions' => $dim_map,
                'sizes'      => $sizes,
                'time_dim'   => $time_dim,
                'values'     => $values,
            );
        }

        /**
         * Resolve strict dimension selection.
         *
         * @param array<string,mixed> $parsed Parsed dataset.
         * @param array<string,mixed> $config Dataset config.
         * @param string              $country_code Country code.
         * @return array<string,mixed>
         */
        private function resolve_strict_selection( $parsed, $config, $country_code ) {
            $dimensions      = isset( $parsed['dimensions'] ) ? $parsed['dimensions'] : array();
            $time_dim        = isset( $parsed['time_dim'] ) ? (int) $parsed['time_dim'] : -1;
            $selectors       = isset( $config['selectors'] ) && is_array( $config['selectors'] ) ? $config['selectors'] : array();
            $sum_dimension   = isset( $config['sum_dimension'] ) ? strtolower( (string) $config['sum_dimension'] ) : '';
            $allow_series    = isset( $config['allow_series_dimensions'] ) && is_array( $config['allow_series_dimensions'] ) ? $config['allow_series_dimensions'] : array();
            $series_rules    = isset( $config['series_dimension_rules'] ) && is_array( $config['series_dimension_rules'] ) ? $config['series_dimension_rules'] : array();
            $fixed_positions = array();
            $sum_positions   = array();
            $unit_label      = '';
            $residual_dims   = array();

            foreach ( $dimensions as $index => $dimension ) {
                $dim_id = strtolower( (string) $dimension['id'] );
                if ( (int) $index === $time_dim ) {
                    continue;
                }

                if ( 'geo' === $dim_id ) {
                    $position = $this->find_code_position( $dimension, $country_code );
                    if ( null === $position ) {
                        return array(
                            'status'  => 'unavailable',
                            'message' => __( 'The selected country is not present in this official Eurostat dataset.', 'noesia-smart-waiter' ),
                        );
                    }
                    $fixed_positions[ $index ] = $position;
                    continue;
                }

                if ( $sum_dimension && $dim_id === $sum_dimension ) {
                    $preferred_total = $this->find_total_positions( $dimension );
                    if ( ! empty( $preferred_total ) ) {
                        $fixed_positions[ $index ] = $preferred_total[0];
                    } else {
                        $sum_positions[ $index ] = $this->find_non_total_positions( $dimension );
                    }
                    continue;
                }

                if ( isset( $selectors[ $dim_id ] ) ) {
                    $position = $this->resolve_dimension_position( $dimension, $selectors[ $dim_id ] );
                    if ( null === $position ) {
                        if ( 1 === (int) $dimension['size'] ) {
                            $fixed_positions[ $index ] = 0;
                            continue;
                        }
                        $residual_dims[ $index ] = $dimension;
                        continue;
                    }
                    $fixed_positions[ $index ] = $position;
                    if ( 'unit' === $dim_id ) {
                        $code       = isset( $dimension['codes'][ $position ] ) ? (string) $dimension['codes'][ $position ] : '';
                        $unit_label = isset( $dimension['labels'][ $code ] ) ? (string) $dimension['labels'][ $code ] : $code;
                    }
                    continue;
                }

                if ( 1 === (int) $dimension['size'] ) {
                    $fixed_positions[ $index ] = 0;
                    continue;
                }

                $residual_dims[ $index ] = $dimension;
            }

            if ( empty( $unit_label ) ) {
                $unit_label = __( 'Official unit', 'noesia-smart-waiter' );
            }

            if ( empty( $residual_dims ) ) {
                return array(
                    'status'     => 'ok',
                    'unit_label' => $unit_label,
                    'series'     => array(
                        array(
                            'positions' => $fixed_positions,
                            'label'     => __( 'Official series', 'noesia-smart-waiter' ),
                            'selector'  => '',
                            'sum'       => $sum_positions,
                        ),
                    ),
                );
            }

            if ( 1 !== count( $residual_dims ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'The official dataset still contains multiple unresolved dimensions after strict filtering, so the series is hidden to avoid misinterpretation.', 'noesia-smart-waiter' ),
                );
            }

            $residual_index = (int) array_key_first( $residual_dims );
            $dimension      = $residual_dims[ $residual_index ];
            $dim_id         = strtolower( (string) $dimension['id'] );

            if ( empty( $allow_series ) || ! in_array( $dim_id, $allow_series, true ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'The official dataset still contains an unresolved dimension that is not approved for explicit series display.', 'noesia-smart-waiter' ),
                );
            }

            $rule              = isset( $series_rules[ $dim_id ] ) && is_array( $series_rules[ $dim_id ] ) ? $series_rules[ $dim_id ] : array();
            $series_candidates = $this->resolve_series_positions_for_dimension( $dimension, $rule );

            if ( empty( $series_candidates ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'No deterministic official series could be selected from the remaining dataset dimension.', 'noesia-smart-waiter' ),
                );
            }

            $series = array();
            foreach ( $series_candidates as $candidate ) {
                $position = isset( $candidate['position'] ) ? (int) $candidate['position'] : null;
                if ( null === $position ) {
                    continue;
                }
                $label = isset( $candidate['label'] ) ? (string) $candidate['label'] : __( 'Official series', 'noesia-smart-waiter' );
                if ( 'unit' === $dim_id && ! $this->is_preferred_occupancy_label( $label, isset( $candidate['code'] ) ? (string) $candidate['code'] : '' ) ) {
                    continue;
                }
                $positions                  = $fixed_positions;
                $positions[ $residual_index ] = $position;
                $series[]                    = array(
                    'positions' => $positions,
                    'label'     => $label,
                    'selector'  => isset( $candidate['code'] ) ? (string) $candidate['code'] : $label,
                    'sum'       => $sum_positions,
                );
            }

            if ( empty( $series ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'The remaining official series did not pass the strict display rules for this dataset.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status'     => 'ok',
                'unit_label' => $unit_label,
                'series'     => $series,
            );
        
        }


        /**
         * Resolve allowed explicit series from one residual dimension.
         *
         * @param array<string,mixed> $dimension Dimension metadata.
         * @param array<string,mixed> $rule Dimension rule.
         * @return array<int,array<string,mixed>>
         */
        private function resolve_series_positions_for_dimension( $dimension, $rule ) {
            $codes      = isset( $dimension['codes'] ) ? $dimension['codes'] : array();
            $labels     = isset( $dimension['labels'] ) ? $dimension['labels'] : array();
            $series     = array();
            $positions  = array();
            $label_map  = isset( $rule['labels'] ) && is_array( $rule['labels'] ) ? $rule['labels'] : array();

            if ( ! empty( $rule['preferred_codes'] ) && is_array( $rule['preferred_codes'] ) ) {
                foreach ( $rule['preferred_codes'] as $preferred_code ) {
                    foreach ( $codes as $position => $code ) {
                        if ( strtolower( (string) $code ) !== strtolower( (string) $preferred_code ) ) {
                            continue;
                        }
                        $positions[] = array(
                            'position' => (int) $position,
                            'code'     => (string) $code,
                            'label'    => isset( $label_map[ $preferred_code ] ) ? (string) $label_map[ $preferred_code ] : ( isset( $labels[ $code ] ) ? (string) $labels[ $code ] : (string) $code ),
                        );
                    }
                }
            }

            if ( empty( $positions ) && ! empty( $rule['label_patterns'] ) && is_array( $rule['label_patterns'] ) ) {
                foreach ( $codes as $position => $code ) {
                    $label = isset( $labels[ $code ] ) ? strtolower( (string) $labels[ $code ] ) : strtolower( (string) $code );
                    foreach ( $rule['label_patterns'] as $pattern ) {
                        if ( false !== strpos( $label, strtolower( (string) $pattern ) ) ) {
                            $positions[] = array(
                                'position' => (int) $position,
                                'code'     => (string) $code,
                                'label'    => isset( $labels[ $code ] ) ? (string) $labels[ $code ] : (string) $code,
                            );
                            break;
                        }
                    }
                }
            }

            if ( empty( $positions ) ) {
                foreach ( $codes as $position => $code ) {
                    $positions[] = array(
                        'position' => (int) $position,
                        'code'     => (string) $code,
                        'label'    => isset( $labels[ $code ] ) ? (string) $labels[ $code ] : (string) $code,
                    );
                }
            }

            foreach ( $positions as $candidate ) {
                $series[] = $candidate;
            }

            return $series;
        }

        /**
         * Select the primary latest point for overview cards.
         *
         * @param array<string,mixed> $dataset Dataset payload.
         * @return array<string,mixed>
         */
        private function select_primary_series_latest( $dataset ) {
            if ( empty( $dataset['series'] ) || ! is_array( $dataset['series'] ) ) {
                return array();
            }

            $series = $dataset['series'];
            usort(
                $series,
                function ( $left, $right ) {
                    return $this->series_label_rank( isset( $left['label'] ) ? (string) $left['label'] : '' ) <=> $this->series_label_rank( isset( $right['label'] ) ? (string) $right['label'] : '' );
                }
            );

            foreach ( $series as $item ) {
                if ( ! empty( $item['latest']['formatted'] ) ) {
                    return $item['latest'];
                }
            }

            return array();
        }

        /**
         * Get overview-card rank for one series label.
         *
         * @param string $label Series label.
         * @return int
         */
        private function series_label_rank( $label ) {
            $label = strtolower( $label );

            if ( false !== strpos( $label, 'total' ) ) {
                return 0;
            }
            if ( false !== strpos( $label, 'bed-place' ) || false !== strpos( $label, 'bed places' ) || false !== strpos( $label, 'bedplaces' ) ) {
                return 1;
            }
            if ( false !== strpos( $label, 'bedroom' ) || false !== strpos( $label, 'rooms' ) ) {
                return 2;
            }
            if ( false !== strpos( $label, 'domestic' ) ) {
                return 3;
            }
            if ( false !== strpos( $label, 'foreign' ) ) {
                return 4;
            }

            return 10;
        }

        /**
         * Resolve one dimension position.
         *
         * @param array<string,mixed> $dimension Dimension metadata.
         * @param array<string,mixed> $rule Dimension rule.
         * @return int|null
         */
        private function resolve_dimension_position( $dimension, $rule ) {
            $codes  = isset( $dimension['codes'] ) ? $dimension['codes'] : array();
            $labels = isset( $dimension['labels'] ) ? $dimension['labels'] : array();

            if ( ! empty( $rule['codes'] ) && is_array( $rule['codes'] ) ) {
                foreach ( $rule['codes'] as $candidate_code ) {
                    foreach ( $codes as $position => $code ) {
                        if ( strtolower( (string) $code ) === strtolower( (string) $candidate_code ) ) {
                            return (int) $position;
                        }
                    }
                }
            }

            if ( ! empty( $rule['label_patterns'] ) && is_array( $rule['label_patterns'] ) ) {
                foreach ( $codes as $position => $code ) {
                    $label = isset( $labels[ $code ] ) ? strtolower( (string) $labels[ $code ] ) : strtolower( (string) $code );
                    if ( ! empty( $rule['reject_labels'] ) && is_array( $rule['reject_labels'] ) ) {
                        foreach ( $rule['reject_labels'] as $reject ) {
                            if ( false !== strpos( $label, strtolower( (string) $reject ) ) ) {
                                continue 2;
                            }
                        }
                    }
                    foreach ( $rule['label_patterns'] as $pattern ) {
                        if ( false !== strpos( $label, strtolower( (string) $pattern ) ) ) {
                            return (int) $position;
                        }
                    }
                }
            }

            if ( 1 === (int) $dimension['size'] ) {
                return 0;
            }

            return null;
        }

        /**
         * Find code position.
         *
         * @param array<string,mixed> $dimension Dimension.
         * @param string              $code Code.
         * @return int|null
         */
        private function find_code_position( $dimension, $code ) {
            foreach ( $dimension['codes'] as $position => $candidate ) {
                if ( strtolower( (string) $candidate ) === strtolower( (string) $code ) ) {
                    return (int) $position;
                }
            }
            return null;
        }

        /**
         * Find total positions.
         *
         * @param array<string,mixed> $dimension Dimension.
         * @return array<int,int>
         */
        private function find_total_positions( $dimension ) {
            $matches = array();
            foreach ( $dimension['codes'] as $position => $code ) {
                $label = isset( $dimension['labels'][ $code ] ) ? strtolower( (string) $dimension['labels'][ $code ] ) : strtolower( (string) $code );
                $code  = strtolower( (string) $code );
                if ( in_array( $code, array( 'total', 'tot' ), true ) || false !== strpos( $label, 'total' ) || false !== strpos( $label, 'all' ) ) {
                    $matches[] = (int) $position;
                }
            }
            return $matches;
        }

        /**
         * Find non-total positions.
         *
         * @param array<string,mixed> $dimension Dimension.
         * @return array<int,int>
         */
        private function find_non_total_positions( $dimension ) {
            $matches = array();
            foreach ( $dimension['codes'] as $position => $code ) {
                $label = isset( $dimension['labels'][ $code ] ) ? strtolower( (string) $dimension['labels'][ $code ] ) : strtolower( (string) $code );
                $code  = strtolower( (string) $code );
                if ( in_array( $code, array( 'total', 'tot' ), true ) || false !== strpos( $label, 'total' ) || false !== strpos( $label, 'all' ) ) {
                    continue;
                }
                $matches[] = (int) $position;
            }
            return $matches;
        }

        /**
         * Check whether occupancy label is preferred.
         *
         * @param string $label Label.
         * @param string $code Code.
         * @return bool
         */
        private function is_preferred_occupancy_label( $label, $code ) {
            $haystack = strtolower( $label . ' ' . $code );
            return false !== strpos( $haystack, 'bed' ) || false !== strpos( $haystack, 'bedroom' ) || false !== strpos( $haystack, 'place' ) || false !== strpos( $haystack, 'room' );
        }

        /**
         * Extract one series rows.
         *
         * @param array<string,mixed> $parsed Parsed dataset.
         * @param array<string,mixed> $series_config Series config.
         * @param string              $mode Mode.
         * @return array<int,array<string,mixed>>
         */
        private function extract_series_rows( $parsed, $series_config, $mode ) {
            $dimensions = $parsed['dimensions'];
            $sizes      = $parsed['sizes'];
            $time_dim   = (int) $parsed['time_dim'];
            $values     = $parsed['values'];
            $time_codes = isset( $dimensions[ $time_dim ]['codes'] ) ? $dimensions[ $time_dim ]['codes'] : array();
            $rows       = array();

            foreach ( $time_codes as $time_position => $time_code ) {
                $positions              = isset( $series_config['positions'] ) && is_array( $series_config['positions'] ) ? $series_config['positions'] : array();
                $positions[ $time_dim ] = (int) $time_position;
                $value                  = 0.0;

                if ( ! empty( $series_config['sum'] ) && is_array( $series_config['sum'] ) ) {
                    $sum_sets = $series_config['sum'];
                    $value    = $this->sum_series_value( $positions, $sum_sets, $sizes, $values );
                } else {
                    ksort( $positions );
                    $flat_index = $this->calculate_jsonstat_flat_index( $positions, $sizes );
                    $raw_value  = $this->jsonstat_value_at_index( $values, $flat_index );
                    if ( null === $raw_value || '' === $raw_value ) {
                        continue;
                    }
                    $value = (float) $raw_value;
                }

                $rows[] = array(
                    'period_raw'   => (string) $time_code,
                    'period_label' => $this->format_period_label( (string) $time_code ),
                    'value'        => in_array( $mode, array( 'percentage', 'percent' ), true ) ? round( $value, 1 ) : round( $value ),
                    'formatted'    => $this->format_value( $value, $mode ),
                );
            }

            usort(
                $rows,
                function ( $left, $right ) {
                    return strcmp( $this->sortable_period_key( (string) $left['period_raw'] ), $this->sortable_period_key( (string) $right['period_raw'] ) );
                }
            );

            return array_values( array_slice( $rows, -12 ) );
        }

        /**
         * Sum one series across a dimension.
         *
         * @param array<int,int>        $base_positions Base positions.
         * @param array<int,array<int>> $sum_sets Sum dimensions.
         * @param array<int,int>        $sizes Sizes.
         * @param array<int|numeric-string,mixed> $values Values.
         * @return float
         */
        private function sum_series_value( $base_positions, $sum_sets, $sizes, $values ) {
            $expanded = array( $base_positions );

            foreach ( $sum_sets as $dim_index => $positions ) {
                $next = array();
                foreach ( $expanded as $combo ) {
                    foreach ( $positions as $position ) {
                        $combo[ $dim_index ] = (int) $position;
                        $next[] = $combo;
                    }
                }
                $expanded = $next;
            }

            $sum = 0.0;
            foreach ( $expanded as $combo ) {
                ksort( $combo );
                $flat_index = $this->calculate_jsonstat_flat_index( $combo, $sizes );
                $raw_value  = $this->jsonstat_value_at_index( $values, $flat_index );
                if ( null === $raw_value || '' === $raw_value ) {
                    continue;
                }
                $sum += (float) $raw_value;
            }

            return $sum;
        }

        /**
         * Read one JSON-stat value.
         *
         * @param mixed $values Values.
         * @param int   $index Flat index.
         * @return mixed|null
         */
        private function jsonstat_value_at_index( $values, $index ) {
            if ( is_array( $values ) ) {
                if ( array_key_exists( $index, $values ) ) {
                    return $values[ $index ];
                }
                if ( array_key_exists( (string) $index, $values ) ) {
                    return $values[ (string) $index ];
                }
            }
            return null;
        }

        /**
         * Calculate flat JSON-stat index.
         *
         * @param array<int,int> $positions Positions.
         * @param array<int,int> $sizes Sizes.
         * @return int
         */
        private function calculate_jsonstat_flat_index( $positions, $sizes ) {
            $index = 0;
            $count = count( $sizes );

            for ( $i = 0; $i < $count; $i++ ) {
                $multiplier = 1;
                for ( $j = $i + 1; $j < $count; $j++ ) {
                    $multiplier *= isset( $sizes[ $j ] ) ? (int) $sizes[ $j ] : 1;
                }
                $index += ( isset( $positions[ $i ] ) ? (int) $positions[ $i ] : 0 ) * $multiplier;
            }

            return $index;
        }

        /**
         * Build latest point.
         *
         * @param array<int,array<string,mixed>> $rows Rows.
         * @param string                         $mode Mode.
         * @return array<string,mixed>
         */
        private function build_latest_point( $rows, $mode ) {
            $latest = end( $rows );
            reset( $rows );
            if ( ! is_array( $latest ) ) {
                return array(
                    'period_raw'   => '',
                    'period_label' => '',
                    'value'        => '',
                    'formatted'    => '—',
                );
            }

            return array(
                'period_raw'   => isset( $latest['period_raw'] ) ? (string) $latest['period_raw'] : '',
                'period_label' => isset( $latest['period_label'] ) ? (string) $latest['period_label'] : '',
                'value'        => isset( $latest['value'] ) ? $latest['value'] : '',
                'formatted'    => isset( $latest['formatted'] ) ? (string) $latest['formatted'] : $this->format_value( isset( $latest['value'] ) ? $latest['value'] : 0, $mode ),
            );
        }

        /**
         * Aggregate monthly rows to quarters where applicable.
         *
         * @param array<int,array<string,mixed>> $rows Rows.
         * @param string                         $mode Mode.
         * @return array<int,array<string,mixed>>
         */
        private function aggregate_rows_to_quarters( $rows, $mode ) {
            $quarters = array();

            foreach ( $rows as $row ) {
                $period = isset( $row['period_raw'] ) ? (string) $row['period_raw'] : '';
                if ( ! preg_match( '/^(\d{4})[-]?M?(\d{2})$/', $period, $matches ) && ! preg_match( '/^(\d{4})-(\d{2})$/', $period, $matches ) ) {
                    continue;
                }
                $year    = (int) $matches[1];
                $month   = (int) $matches[2];
                $quarter = (int) ceil( $month / 3 );
                $key     = $year . '-Q' . $quarter;
                if ( ! isset( $quarters[ $key ] ) ) {
                    $quarters[ $key ] = array(
                        'period'    => $year . ' Q' . $quarter,
                        'value'     => 0,
                        'count'     => 0,
                        'formatted' => '',
                    );
                }
                $quarters[ $key ]['value'] += (float) ( isset( $row['value'] ) ? $row['value'] : 0 );
                $quarters[ $key ]['count'] += 1;
            }

            foreach ( $quarters as &$quarter ) {
                if ( in_array( $mode, array( 'percentage', 'percent' ), true ) && ! empty( $quarter['count'] ) ) {
                    $quarter['value'] = round( $quarter['value'] / $quarter['count'], 1 );
                }
                $quarter['formatted'] = $this->format_value( $quarter['value'], $mode );
                unset( $quarter['count'] );
            }
            unset( $quarter );

            return array_values( array_slice( $quarters, -4 ) );
        }

        /**
         * Format value.
         *
         * @param float|int|string $value Value.
         * @param string           $mode Mode.
         * @return string
         */
        private function format_value( $value, $mode ) {
            $numeric = (float) $value;
            if ( in_array( $mode, array( 'percentage', 'percent' ), true ) ) {
                return number_format_i18n( $numeric, 1 ) . '%';
            }
            return number_format_i18n( $numeric, 0 );
        }

        /**
         * Format period label.
         *
         * @param string $period Raw period.
         * @return string
         */
        private function format_period_label( $period ) {
            if ( preg_match( '/^(\d{4})-(\d{2})$/', $period, $matches ) || preg_match( '/^(\d{4})M(\d{2})$/', $period, $matches ) ) {
                $year  = (int) $matches[1];
                $month = (int) $matches[2];
                return wp_date( 'M Y', mktime( 0, 0, 0, $month, 1, $year ) );
            }
            if ( preg_match( '/^(\d{4})-Q([1-4])$/', $period, $matches ) || preg_match( '/^(\d{4})Q([1-4])$/', $period, $matches ) ) {
                return $matches[1] . ' Q' . $matches[2];
            }
            if ( preg_match( '/^(\d{4})$/', $period ) ) {
                return $period;
            }
            return $period;
        }

        /**
         * Build sortable period key.
         *
         * @param string $period Period.
         * @return string
         */
        private function sortable_period_key( $period ) {
            if ( preg_match( '/^(\d{4})-(\d{2})$/', $period, $matches ) || preg_match( '/^(\d{4})M(\d{2})$/', $period, $matches ) ) {
                return $matches[1] . $matches[2];
            }
            if ( preg_match( '/^(\d{4})-Q([1-4])$/', $period, $matches ) || preg_match( '/^(\d{4})Q([1-4])$/', $period, $matches ) ) {
                return $matches[1] . 'Q' . $matches[2];
            }
            return $period;
        }

        /**
         * Get strict development payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_development_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'development_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $wb_country = $this->get_world_bank_country_code( $country_code );
            if ( '' === $wb_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'World Bank Indicators API (WDI)',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict World Bank country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_development_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_world_bank_indicator_payload( $config, $wb_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'World Bank Indicators API (WDI)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'World Bank mode is pinned to WDI source 2 and uses only fixed indicator codes. No alternative series, substitutions, or guessed mappings are used.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }


        /**
         * Get digital and connectivity payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_digital_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'digital_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $wb_country = $this->get_world_bank_country_code( $country_code );
            if ( '' === $wb_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'World Bank Indicators API (WDI)',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict World Bank country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_digital_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_world_bank_indicator_payload( $config, $wb_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'World Bank Indicators API (WDI)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'Digital / Connectivity mode is pinned to World Bank WDI source 2 and uses only fixed official ICT indicator codes. The enabled cards stay explicit about whether the underlying source is ITU or another official provider exposed through WDI metadata.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }



        /**
         * Get population and society payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_population_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'population_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $wb_country = $this->get_world_bank_country_code( $country_code );
            if ( '' === $wb_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'World Bank Indicators API (WDI)',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict World Bank country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_population_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_world_bank_indicator_payload( $config, $wb_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'World Bank Indicators API (WDI)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'Population & Society mode is pinned to World Bank WDI source 2 and uses only fixed demographic and social indicator codes. No alternative series, substitutions, or guessed mappings are used.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }


        /**
         * Get OECD insights payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_oecd_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'oecd_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $oecd_country = $this->get_oecd_country_code( $country_code );
            if ( '' === $oecd_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'OECD Data Explorer API',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict OECD country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_oecd_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_oecd_indicator_payload( $config, $oecd_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'OECD Data Explorer API (strict labour and activity selections)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'OECD Insights uses only fixed OECD Data Explorer SDMX selections in this stage. This tab is intentionally labour-and-activity focused until broader OECD slices are verified country by country.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }



        /**
         * Get health payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_health_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'health_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $who_country = $this->get_who_country_code( $country_code );
            if ( '' === $who_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'WHO Global Health Observatory (GHO OData API)',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict WHO country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_health_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_who_health_indicator_payload( $config, $who_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'WHO Global Health Observatory (GHO OData API)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'Health uses exact WHO Global Health Observatory indicator matches only. Each card resolves one official WHO indicator title to one official WHO code and shows only country rows with usable official values.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }

        /**
         * Get medical evidence language options.
         *
         * @return array<string,string>
         */
        public function get_medical_language_options() {
            return array(
                'any'        => __( 'Official record language', 'noesia-smart-waiter' ),
                'english'    => __( 'English articles', 'noesia-smart-waiter' ),
                'spanish'    => __( 'Spanish articles', 'noesia-smart-waiter' ),
                'french'     => __( 'French articles', 'noesia-smart-waiter' ),
                'german'     => __( 'German articles', 'noesia-smart-waiter' ),
                'italian'    => __( 'Italian articles', 'noesia-smart-waiter' ),
                'portuguese' => __( 'Portuguese articles', 'noesia-smart-waiter' ),
                'greek'      => __( 'Greek articles', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Normalize medical evidence language.
         *
         * @param string $language Requested language.
         * @return string
         */
        private function normalize_medical_language( $language ) {
            $language = sanitize_key( (string) $language );
            $options  = $this->get_medical_language_options();
            return isset( $options[ $language ] ) ? $language : 'any';
        }

        /**
         * Get medical evidence payload.
         *
         * @param string $query Search query.
         * @param bool   $force Force refresh.
         * @param string $language Requested PubMed article language filter.
         * @return array<string,mixed>
         */
        public function get_medical_evidence_payload( $query = '', $force = false, $language = 'any' ) {
            $settings = $this->get_settings();
            $query    = trim( sanitize_text_field( (string) $query ) );
            $language = 'any';
            $lang_map = $this->get_medical_language_options();

            if ( '' === $query ) {
                return array(
                    'status'         => 'idle',
                    'source'         => 'PubMed + ClinicalTrials.gov',
                    'query'          => '',
                    'language'       => $language,
                    'language_label' => isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : (string) $lang_map['any'],
                    'overview_cards' => array(),
                    'pubmed'         => array(
                        'items'  => array(),
                        'total'  => 0,
                        'notice' => __( 'Enter a medical topic to search PubMed and ClinicalTrials.gov inside the app.', 'noesia-smart-waiter' ),
                    ),
                    'trials'         => array(
                        'items'  => array(),
                        'total'  => 0,
                        'notice' => __( 'Enter a medical topic to search official clinical trial registry records.', 'noesia-smart-waiter' ),
                    ),
                    'note'           => __( 'Loco translates the interface only. External PubMed and ClinicalTrials.gov record content is not translated by Loco. Medical Evidence stays on the stable default official returned language flow.', 'noesia-smart-waiter' ),
                );
            }

            if ( strlen( $query ) < 3 ) {
                return array(
                    'status'         => 'unavailable',
                    'source'         => 'PubMed + ClinicalTrials.gov',
                    'query'          => $query,
                    'language'       => $language,
                    'language_label' => isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : (string) $lang_map['any'],
                    'overview_cards' => array(),
                    'pubmed'         => array(
                        'items'  => array(),
                        'total'  => 0,
                        'notice' => __( 'Use at least 3 characters so the medical evidence search stays intentional and readable.', 'noesia-smart-waiter' ),
                    ),
                    'trials'         => array(
                        'items'  => array(),
                        'total'  => 0,
                        'notice' => __( 'Use at least 3 characters so the clinical trials search can return meaningful official records.', 'noesia-smart-waiter' ),
                    ),
                    'note'           => __( 'Short search terms are blocked in this build to reduce noise and accidental broad queries.', 'noesia-smart-waiter' ),
                );
            }

            $cache_key = self::CACHE_PREFIX . 'medical_' . md5( strtolower( $query . '|' . $language ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $payload = $this->fetch_medical_evidence_payload( $query, $language );
            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }

        /**
         * Get compare countries payload.
         *
         * @param array<int|string,mixed> $country_codes Requested countries.
         * @param bool                    $force Force refresh.
         * @return array<string,mixed>
         */
        /**
         * Get NIH research payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_nih_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'nih_' . strtolower( $country_code );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $payload = $this->fetch_nih_payload( $country_code, $country_name );
            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }

        public function get_compare_payload( $country_codes = array(), $force = false ) {
            $settings      = $this->get_settings();
            $country_codes = $this->sanitize_compare_country_codes( $country_codes );
            $cache_key     = self::CACHE_PREFIX . 'compare_' . md5( implode( '|', $country_codes ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $countries = $this->get_country_options();
            $metrics   = array();
            foreach ( $this->get_compare_metric_configs() as $metric_config ) {
                $metrics[] = $this->build_compare_metric_payload( $metric_config, $country_codes, $countries, absint( $settings['refresh_hours'] ) );
            }

            $country_labels = array();
            foreach ( $country_codes as $country_code ) {
                if ( isset( $countries[ $country_code ] ) ) {
                    $country_labels[] = (string) $countries[ $country_code ];
                }
            }

            $payload = array(
                'status'         => 'ok',
                'scope'          => implode( ', ', $country_labels ),
                'overview_cards' => array(
                    array(
                        'label' => __( 'Countries compared', 'noesia-smart-waiter' ),
                        'value' => (string) count( $country_codes ),
                        'meta'  => __( 'Strict country set', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Metrics enabled', 'noesia-smart-waiter' ),
                        'value' => (string) count( $metrics ),
                        'meta'  => __( 'Fixed official comparison deck', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Source stack', 'noesia-smart-waiter' ),
                        'value' => 'Eurostat + World Bank + IMF',
                        'meta'  => __( 'Strict official APIs', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Refresh Window', 'noesia-smart-waiter' ),
                        'value' => absint( $settings['refresh_hours'] ) . 'h',
                        'meta'  => __( 'Transient cache', 'noesia-smart-waiter' ),
                    ),
                ),
                'metrics'        => $metrics,
                'note'           => __( 'Compare Countries uses only fixed official metrics that are already enabled elsewhere in Global Data Hub. Each row keeps its own latest official reference period, so countries are never forced into fake synchronized dates when the providers publish different latest periods.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }

        /**
         * Build one compare metric payload.
         *
         * @param array<string,mixed>  $metric_config Metric config.
         * @param array<int,string>    $country_codes Country codes.
         * @param array<string,string> $countries Country labels.
         * @param int                  $refresh_hours Refresh hours.
         * @return array<string,mixed>
         */
        private function build_compare_metric_payload( $metric_config, $country_codes, $countries, $refresh_hours ) {
            $rows = array();
            foreach ( $country_codes as $country_code ) {
                $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
                $rows[]       = $this->build_compare_country_row( $metric_config, $country_code, $country_name, $refresh_hours );
            }

            return array(
                'title'         => (string) $metric_config['title'],
                'definition'    => (string) $metric_config['definition'],
                'provider'      => (string) $metric_config['provider_label'],
                'code'          => (string) $metric_config['code'],
                'coverage_note' => __( 'Latest official values only', 'noesia-smart-waiter' ),
                'notice'        => (string) $metric_config['notice'],
                'rows'          => $rows,
            );
        }

        /**
         * Build one compare row for one country.
         *
         * @param array<string,mixed> $metric_config Metric config.
         * @param string              $country_code Country code.
         * @param string              $country_name Country name.
         * @param int                 $refresh_hours Refresh hours.
         * @return array<string,string>
         */
        private function build_compare_country_row( $metric_config, $country_code, $country_name, $refresh_hours ) {
            $provider = isset( $metric_config['provider'] ) ? (string) $metric_config['provider'] : '';

            if ( 'world_bank' === $provider ) {
                $mapped = $this->get_world_bank_country_code( $country_code );
                if ( '' === $mapped ) {
                    return $this->build_compare_unavailable_row( $country_code, $country_name, __( 'Country not mapped to World Bank in this build.', 'noesia-smart-waiter' ) );
                }
                $payload = $this->fetch_world_bank_indicator_payload( $metric_config['indicator'], $mapped, $country_name );
                return $this->build_compare_row_from_indicator_payload( $payload, $country_code, $country_name );
            }

            if ( 'imf' === $provider ) {
                $mapped = $this->get_imf_country_code( $country_code );
                if ( '' === $mapped ) {
                    return $this->build_compare_unavailable_row( $country_code, $country_name, __( 'Country not mapped to IMF in this build.', 'noesia-smart-waiter' ) );
                }
                $payload = $this->fetch_imf_macro_indicator_payload( $metric_config['indicator'], $mapped, $country_name );
                return $this->build_compare_row_from_indicator_payload( $payload, $country_code, $country_name );
            }

            if ( 'eurostat' === $provider ) {
                $payload = $this->fetch_strict_dataset( $metric_config['dataset'], $country_code, $country_name, $refresh_hours );
                return $this->build_compare_row_from_dataset_payload( $payload, $country_code, $country_name );
            }

            return $this->build_compare_unavailable_row( $country_code, $country_name, __( 'Unsupported compare provider.', 'noesia-smart-waiter' ) );
        }

        /**
         * Build compare row from indicator payload.
         *
         * @param array<string,mixed> $payload Indicator payload.
         * @param string              $country_code Country code.
         * @param string              $country_name Country name.
         * @return array<string,string>
         */
        private function build_compare_row_from_indicator_payload( $payload, $country_code, $country_name ) {
            if ( ! is_array( $payload ) || 'ok' !== ( isset( $payload['status'] ) ? (string) $payload['status'] : '' ) || empty( $payload['latest']['formatted'] ) ) {
                $notice = is_array( $payload ) && ! empty( $payload['notice'] ) ? (string) $payload['notice'] : __( 'Official value unavailable.', 'noesia-smart-waiter' );
                return $this->build_compare_unavailable_row( $country_code, $country_name, $notice );
            }

            return array(
                'country_code'  => $country_code,
                'country_label' => $country_name,
                'formatted'     => (string) $payload['latest']['formatted'],
                'period_label'  => ! empty( $payload['latest']['period_label'] ) ? (string) $payload['latest']['period_label'] : '',
                'detail'        => ! empty( $payload['source'] ) ? (string) $payload['source'] : __( 'Official source', 'noesia-smart-waiter' ),
                'notice'        => ! empty( $payload['notice'] ) ? (string) $payload['notice'] : '',
            );
        }

        /**
         * Build compare row from dataset payload.
         *
         * @param array<string,mixed> $payload Dataset payload.
         * @param string              $country_code Country code.
         * @param string              $country_name Country name.
         * @return array<string,string>
         */
        private function build_compare_row_from_dataset_payload( $payload, $country_code, $country_name ) {
            if ( ! is_array( $payload ) || 'ok' !== ( isset( $payload['status'] ) ? (string) $payload['status'] : '' ) || empty( $payload['series'] ) || ! is_array( $payload['series'] ) ) {
                $notice = is_array( $payload ) && ! empty( $payload['notice'] ) ? (string) $payload['notice'] : __( 'Official value unavailable.', 'noesia-smart-waiter' );
                return $this->build_compare_unavailable_row( $country_code, $country_name, $notice );
            }

            $selected = null;
            foreach ( $payload['series'] as $series ) {
                $selector = isset( $series['selector'] ) ? strtolower( (string) $series['selector'] ) : '';
                $label    = isset( $series['label'] ) ? strtolower( (string) $series['label'] ) : '';
                if ( false !== strpos( $selector, 'bed' ) || false !== strpos( $label, 'bed' ) ) {
                    $selected = $series;
                    break;
                }
            }
            if ( null === $selected ) {
                $selected = $payload['series'][0];
            }

            if ( empty( $selected['latest']['formatted'] ) ) {
                return $this->build_compare_unavailable_row( $country_code, $country_name, __( 'Official value unavailable.', 'noesia-smart-waiter' ) );
            }

            return array(
                'country_code'  => $country_code,
                'country_label' => $country_name,
                'formatted'     => (string) $selected['latest']['formatted'],
                'period_label'  => ! empty( $selected['latest']['period_label'] ) ? (string) $selected['latest']['period_label'] : '',
                'detail'        => ! empty( $selected['label'] ) ? (string) $selected['label'] : __( 'Official series', 'noesia-smart-waiter' ),
                'notice'        => ! empty( $payload['notice'] ) ? (string) $payload['notice'] : '',
            );
        }

        /**
         * Build unavailable compare row.
         *
         * @param string $country_code Country code.
         * @param string $country_name Country name.
         * @param string $notice Notice.
         * @return array<string,string>
         */
        private function build_compare_unavailable_row( $country_code, $country_name, $notice ) {
            return array(
                'country_code'  => $country_code,
                'country_label' => $country_name,
                'formatted'     => '—',
                'period_label'  => '—',
                'detail'        => sanitize_text_field( $notice ),
                'notice'        => sanitize_text_field( $notice ),
            );
        }

        /**
         * Sanitize compare countries.
         *
         * @param array<int|string,mixed> $country_codes Raw country codes.
         * @return array<int,string>
         */
        private function sanitize_compare_country_codes( $country_codes ) {
            $allowed   = $this->get_country_options();
            $sanitized = array();
            foreach ( is_array( $country_codes ) ? $country_codes : array() as $country_code ) {
                $country_code = $this->normalize_country_code( (string) $country_code );
                if ( '' === $country_code || ! isset( $allowed[ $country_code ] ) || in_array( $country_code, $sanitized, true ) ) {
                    continue;
                }
                $sanitized[] = $country_code;
            }

            if ( empty( $sanitized ) ) {
                $sanitized = $this->get_compare_default_country_codes();
            }

            if ( 1 === count( $sanitized ) ) {
                foreach ( $this->get_compare_default_country_codes() as $fallback_country ) {
                    if ( in_array( $fallback_country, $sanitized, true ) ) {
                        continue;
                    }
                    $sanitized[] = $fallback_country;
                    break;
                }
            }

            return array_slice( $sanitized, 0, 4 );
        }

        /**
         * Get compare metric configs.
         *
         * @return array<int,array<string,mixed>>
         */
        private function get_compare_metric_configs() {
            return array(
                array(
                    'provider'       => 'eurostat',
                    'provider_label' => 'Eurostat Statistics API',
                    'code'           => 'tour_occ_mnor',
                    'title'          => __( 'Hotel occupancy rate', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Latest official Eurostat hotel occupancy value, preferring the bed-places series when both bed-places and bedrooms are available.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed Eurostat hotel occupancy dataset is compared here. Countries keep their own latest official period and the preferred compare row uses the bed-places series when it is available.', 'noesia-smart-waiter' ),
                    'dataset'        => array(
                        'cache_key'    => 'occupancy_compare',
                        'dataset_code' => 'tour_occ_mnor',
                        'title'        => __( 'Hotel occupancy rate', 'noesia-smart-waiter' ),
                        'definition'   => __( 'Official monthly occupancy rate for hotels and similar accommodation only.', 'noesia-smart-waiter' ),
                        'mode'         => 'percentage',
                        'last_periods' => 18,
                        'selectors'    => array(
                            'freq'    => array( 'codes' => array( 'M' ) ),
                            'nace_r2' => array( 'codes' => array( 'I551' ) ),
                        ),
                        'allow_series_dimensions' => array( 'unit', 'accom', 'accomunit', 'bedroom', 'bedplace', 'bedroom_bedplace' ),
                        'series_dimension_rules'  => array(
                            'unit' => array(
                                'label_patterns' => array( 'bed', 'bedroom', 'room', 'place' ),
                            ),
                            'accomunit' => array(
                                'label_patterns' => array( 'bed', 'bedroom', 'room', 'place' ),
                                'labels'         => array(
                                    'BED-PL'  => __( 'Bed-place occupancy', 'noesia-smart-waiter' ),
                                    'BEDROOM' => __( 'Bedroom occupancy', 'noesia-smart-waiter' ),
                                ),
                            ),
                        ),
                    ),
                ),
                array(
                    'provider'       => 'world_bank',
                    'provider_label' => 'World Bank Indicators API (WDI)',
                    'code'           => 'NY.GDP.PCAP.CD',
                    'title'          => __( 'GDP per capita (current US$)', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Gross domestic product divided by midyear population, in current U.S. dollars.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed World Bank WDI source 2 indicator code is compared here.', 'noesia-smart-waiter' ),
                    'indicator'      => array(
                        'code'       => 'NY.GDP.PCAP.CD',
                        'title'      => __( 'GDP per capita (current US$)', 'noesia-smart-waiter' ),
                        'definition' => __( 'Gross domestic product divided by midyear population, in current U.S. dollars.', 'noesia-smart-waiter' ),
                        'source'     => __( 'World Bank WDI / national accounts data', 'noesia-smart-waiter' ),
                        'mode'       => 'usd',
                    ),
                ),
                array(
                    'provider'       => 'world_bank',
                    'provider_label' => 'World Bank Indicators API (WDI)',
                    'code'           => 'SP.DYN.LE00.IN',
                    'title'          => __( 'Life expectancy', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Life expectancy at birth, total (years).', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed World Bank WDI source 2 indicator code is compared here.', 'noesia-smart-waiter' ),
                    'indicator'      => array(
                        'code'       => 'SP.DYN.LE00.IN',
                        'title'      => __( 'Life expectancy', 'noesia-smart-waiter' ),
                        'definition' => __( 'Life expectancy at birth, total (years).', 'noesia-smart-waiter' ),
                        'source'     => __( 'World Bank WDI / UN Population Division and national statistical sources', 'noesia-smart-waiter' ),
                        'mode'       => 'years',
                    ),
                ),
                array(
                    'provider'       => 'world_bank',
                    'provider_label' => 'World Bank Indicators API (WDI)',
                    'code'           => 'IT.NET.USER.ZS',
                    'title'          => __( 'Internet users', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Individuals using the Internet as a percentage of population.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed World Bank WDI source 2 indicator code is compared here.', 'noesia-smart-waiter' ),
                    'indicator'      => array(
                        'code'       => 'IT.NET.USER.ZS',
                        'title'      => __( 'Internet users', 'noesia-smart-waiter' ),
                        'definition' => __( 'Individuals using the Internet (% of population).', 'noesia-smart-waiter' ),
                        'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU)', 'noesia-smart-waiter' ),
                        'mode'       => 'percentage',
                    ),
                ),
                array(
                    'provider'       => 'imf',
                    'provider_label' => 'IMF DataMapper / World Economic Outlook (WEO)',
                    'code'           => 'NGDP_RPCH',
                    'title'          => __( 'Real GDP growth', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Gross domestic product, constant prices, annual percent change.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed IMF DataMapper WEO indicator code is compared here.', 'noesia-smart-waiter' ),
                    'indicator'      => array(
                        'code'       => 'NGDP_RPCH',
                        'title'      => __( 'Real GDP growth', 'noesia-smart-waiter' ),
                        'definition' => __( 'Gross domestic product, constant prices, annual percent change.', 'noesia-smart-waiter' ),
                        'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                        'mode'       => 'percentage',
                    ),
                ),
                array(
                    'provider'       => 'imf',
                    'provider_label' => 'IMF DataMapper / World Economic Outlook (WEO)',
                    'code'           => 'PCPIPCH',
                    'title'          => __( 'Inflation rate, average consumer prices', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Average consumer price inflation, annual percent change.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only the fixed IMF DataMapper WEO indicator code is compared here.', 'noesia-smart-waiter' ),
                    'indicator'      => array(
                        'code'       => 'PCPIPCH',
                        'title'      => __( 'Inflation rate, average consumer prices', 'noesia-smart-waiter' ),
                        'definition' => __( 'Average consumer price inflation, annual percent change.', 'noesia-smart-waiter' ),
                        'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                        'mode'       => 'percentage',
                    ),
                ),
            );
        }

        /**
         * Get OECD indicator configs.
         *
         * @return array<int,array<string,string>>
         */
        private function get_oecd_indicator_configs() {
            return array(
                array(
                    'dataset_agency' => 'OECD.SDD.TPS',
                    'dataset_id'     => 'DSD_LFS@DF_IALFS_INDIC',
                    'series_code'    => 'UNE_LF_M',
                    'selection'      => '{country}.UNE_LF_M.PT_LF_SUB..Y._T.Y_GE15..M',
                    'title'          => __( 'Monthly unemployment rate', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Unemployment rate as a percentage of the labour force for persons aged 15 years or over.', 'noesia-smart-waiter' ),
                    'source'         => __( 'OECD Data Explorer / Infra-annual labour statistics', 'noesia-smart-waiter' ),
                    'group'          => __( 'OECD labour market', 'noesia-smart-waiter' ),
                    'mode'           => 'percentage',
                    'start_period'   => $this->get_oecd_start_period( 'M', 24 ),
                ),
                array(
                    'dataset_agency' => 'OECD.SDD.TPS',
                    'dataset_id'     => 'DSD_LFS@DF_IALFS_INDIC',
                    'series_code'    => 'UNE_LF_M_YOUTH',
                    'selection'      => '{country}.UNE_LF_M.PT_LF_SUB..Y._T.Y15T24..M',
                    'title'          => __( 'Youth unemployment rate', 'noesia-smart-waiter' ),
                    'definition'     => __( 'Unemployment rate as a percentage of the labour force for young people aged 15 to 24.', 'noesia-smart-waiter' ),
                    'source'         => __( 'OECD Data Explorer / Infra-annual labour statistics', 'noesia-smart-waiter' ),
                    'group'          => __( 'OECD labour market', 'noesia-smart-waiter' ),
                    'mode'           => 'percentage',
                    'start_period'   => $this->get_oecd_start_period( 'M', 24 ),
                ),
                array(
                    'dataset_agency'   => 'OECD.SDD.TPS',
                    'dataset_id'       => 'DSD_LFS@DF_IALFS_INDIC',
                    'series_code'      => 'EMP_WAP',
                    'selection'        => '{country}.EMP_WAP.PT_WAP_SUB..Y._T.Y15T64..Q',
                    'fallback_source'  => __( 'OECD Data Explorer / Infra-annual labour statistics (annual fallback)', 'noesia-smart-waiter' ),
                    'fallback_selection'=> '{country}.EMP_WAP.PT_WAP_SUB..Y._T.Y15T64..A',
                    'title'            => __( 'Employment rate', 'noesia-smart-waiter' ),
                    'definition'       => __( 'Employment rate as a percentage of the working-age population, 15 to 64 years.', 'noesia-smart-waiter' ),
                    'source'           => __( 'OECD Data Explorer / Infra-annual labour statistics', 'noesia-smart-waiter' ),
                    'group'            => __( 'OECD labour market', 'noesia-smart-waiter' ),
                    'mode'             => 'percentage',
                    'start_period'     => $this->get_oecd_start_period( 'Q', 12 ),
                    'fallback_start_period' => $this->get_oecd_start_period( 'A', 8 ),
                ),
                array(
                    'dataset_agency'   => 'OECD.SDD.TPS',
                    'dataset_id'       => 'DSD_LFS@DF_IALFS_INDIC',
                    'series_code'      => 'LF_WAP',
                    'selection'        => '{country}.LF_WAP.PT_WAP_SUB..Y._T.Y15T64..Q',
                    'fallback_source'  => __( 'OECD Data Explorer / Infra-annual labour statistics (annual fallback)', 'noesia-smart-waiter' ),
                    'fallback_selection'=> '{country}.LF_WAP.PT_WAP_SUB..Y._T.Y15T64..A',
                    'title'            => __( 'Labour force participation rate', 'noesia-smart-waiter' ),
                    'definition'       => __( 'Labour force participation as a percentage of the working-age population, 15 to 64 years.', 'noesia-smart-waiter' ),
                    'source'           => __( 'OECD Data Explorer / Infra-annual labour statistics', 'noesia-smart-waiter' ),
                    'group'            => __( 'OECD labour market', 'noesia-smart-waiter' ),
                    'mode'             => 'percentage',
                    'start_period'     => $this->get_oecd_start_period( 'Q', 12 ),
                    'fallback_start_period' => $this->get_oecd_start_period( 'A', 8 ),
                ),
            );
        }

        /**
         * Fetch one OECD indicator payload.
         *
         * @param array<string,string> $config Indicator config.
         * @param string               $oecd_country OECD country code.
         * @param string               $country_name Country label.
         * @return array<string,mixed>
         */
        private function fetch_oecd_indicator_payload( $config, $oecd_country, $country_name ) {
            $request = $this->request_oecd_indicator_csv( $config, $oecd_country );

            if ( 'ok' !== $request['status'] && ! empty( $config['fallback_selection'] ) ) {
                $request = $this->request_oecd_indicator_csv( $config, $oecd_country, true );
            }

            if ( 'ok' !== $request['status'] ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['series_code'],
                    'definition' => (string) $config['definition'],
                    'source'     => 'OECD Data Explorer API',
                    'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'OECD', 'noesia-smart-waiter' ),
                    'scope'      => $country_name,
                    'notice'     => isset( $request['message'] ) ? (string) $request['message'] : __( 'The official OECD request failed.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            $rows = array();
            foreach ( $request['data'] as $row ) {
                if ( ! is_array( $row ) ) {
                    continue;
                }

                $period = isset( $row['TIME_PERIOD'] ) ? sanitize_text_field( (string) $row['TIME_PERIOD'] ) : '';
                if ( '' === $period ) {
                    continue;
                }

                $value = $this->extract_oecd_observation_value( $row );
                if ( null === $value ) {
                    continue;
                }

                $rows[] = array(
                    'period_raw'   => $period,
                    'period_label' => $period,
                    'value'        => $value,
                    'formatted'    => $this->format_oecd_value( $value, (string) $config['mode'] ),
                );
            }

            if ( empty( $rows ) ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['series_code'],
                    'definition' => (string) $config['definition'],
                    'source'     => ! empty( $request['used_fallback'] ) && ! empty( $config['fallback_source'] ) ? (string) $config['fallback_source'] : (string) $config['source'],
                    'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'OECD', 'noesia-smart-waiter' ),
                    'scope'      => $country_name,
                    'notice'     => __( 'The selected fixed OECD series does not currently expose usable values for this country.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            usort(
                $rows,
                static function ( $left, $right ) {
                    return strcmp( (string) $right['period_raw'], (string) $left['period_raw'] );
                }
            );

            $rows   = array_slice( $rows, 0, 8 );
            $latest = $rows[0];

            return array(
                'status'     => 'ok',
                'title'      => (string) $config['title'],
                'code'       => (string) $config['series_code'],
                'definition' => (string) $config['definition'],
                'source'     => ! empty( $request['used_fallback'] ) && ! empty( $config['fallback_source'] ) ? (string) $config['fallback_source'] : (string) $config['source'],
                'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'OECD', 'noesia-smart-waiter' ),
                'scope'      => $country_name,
                'latest'     => $latest,
                'history'    => array_reverse( $rows ),
                /* translators: %s: OECD dataset identifier. */
                'notice'     => sprintf( __( 'Only the fixed OECD Data Explorer selection is shown in this card. Dataset: %1$s.', 'noesia-smart-waiter' ), (string) $config['dataset_id'] ),
            );
        }

        /**
         * Request one OECD indicator selection as CSV.
         *
         * @param array<string,string> $config Indicator config.
         * @param string               $oecd_country OECD country code.
         * @return array<string,mixed>
         */
        private function request_oecd_indicator_csv( $config, $oecd_country, $use_fallback = false ) {
            $selection_key    = $use_fallback && ! empty( $config['fallback_selection'] ) ? 'fallback_selection' : 'selection';
            $start_period_key = $use_fallback && ! empty( $config['fallback_start_period'] ) ? 'fallback_start_period' : 'start_period';
            $selection        = str_replace( '{country}', sanitize_text_field( $oecd_country ), (string) $config[ $selection_key ] );
            $url              = add_query_arg(
                array(
                    'format'                 => 'csvfilewithlabels',
                    'dimensionAtObservation' => 'AllDimensions',
                    'startPeriod'            => isset( $config[ $start_period_key ] ) ? sanitize_text_field( (string) $config[ $start_period_key ] ) : '',
                ),
                'https://sdmx.oecd.org/public/rest/data/' . rawurlencode( sanitize_text_field( (string) $config['dataset_agency'] ) ) . ',' . rawurlencode( sanitize_text_field( (string) $config['dataset_id'] ) ) . '/' . $selection
            );

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 18,
                    'headers' => array(
                        'Accept' => 'text/csv',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'sdmx.oecd.org', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = (string) wp_remote_retrieve_body( $response );

            if ( $code >= 400 ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'OECD Data Explorer returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }

            $rows = $this->parse_oecd_csv_rows( $body );
            if ( empty( $rows ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'OECD Data Explorer returned no readable rows for this strict selection.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status'        => 'ok',
                'data'          => $rows,
                'used_fallback' => (bool) $use_fallback,
            );
        }

        /**
         * Parse OECD CSV rows.
         *
         * @param string $csv Raw CSV.
         * @return array<int,array<string,string>>
         */
        private function parse_oecd_csv_rows( $csv ) {
            $csv = trim( (string) $csv );
            if ( '' === $csv ) {
                return array();
            }

            $lines = preg_split( '/
|
|
/', $csv );
            if ( empty( $lines ) || ! is_array( $lines ) ) {
                return array();
            }

            $headers = str_getcsv( array_shift( $lines ) );
            $headers = is_array( $headers ) ? $headers : array();
            if ( ! empty( $headers ) ) {
                $headers[0] = preg_replace( '/^ï»¿/', '', (string) $headers[0] );
            }

            $rows = array();
            foreach ( $lines as $line ) {
                if ( '' === trim( (string) $line ) ) {
                    continue;
                }
                $values = str_getcsv( $line );
                if ( ! is_array( $values ) || count( $values ) !== count( $headers ) ) {
                    continue;
                }
                $row = array();
                foreach ( $headers as $index => $header ) {
                    $row[ sanitize_text_field( (string) $header ) ] = isset( $values[ $index ] ) ? sanitize_text_field( (string) $values[ $index ] ) : '';
                }
                $rows[] = $row;
            }

            return $rows;
        }

        /**
         * Extract OECD observation value.
         *
         * @param array<string,string> $row Parsed row.
         * @return float|null
         */
        private function extract_oecd_observation_value( $row ) {
            foreach ( array( 'OBS_VALUE', 'ObsValue', 'obs_value', 'VALUE', 'Value' ) as $key ) {
                if ( isset( $row[ $key ] ) && '' !== (string) $row[ $key ] ) {
                    $raw = str_replace( ',', '', (string) $row[ $key ] );
                    return is_numeric( $raw ) ? (float) $raw : null;
                }
            }

            foreach ( $row as $value ) {
                $raw = str_replace( ',', '', (string) $value );
                if ( is_numeric( $raw ) ) {
                    return (float) $raw;
                }
            }

            return null;
        }

        /**
         * Get OECD start period.
         *
         * @param string $frequency Frequency code.
         * @param int    $depth Number of periods to keep.
         * @return string
         */
        private function get_oecd_start_period( $frequency, $depth ) {
            $depth = max( 1, absint( $depth ) );
            switch ( strtoupper( sanitize_text_field( $frequency ) ) ) {
                case 'A':
                    $timestamp = strtotime( '-' . $depth . ' years', current_time( 'timestamp', true ) );
                    if ( false === $timestamp ) {
                        $timestamp = current_time( 'timestamp', true );
                    }
                    return gmdate( 'Y', $timestamp );
                case 'Q':
                    $timestamp = strtotime( '-' . $depth . ' quarters', current_time( 'timestamp', true ) );
                    if ( false === $timestamp ) {
                        $timestamp = current_time( 'timestamp', true );
                    }
                    $month   = (int) gmdate( 'n', $timestamp );
                    $quarter = (int) ceil( $month / 3 );
                    return gmdate( 'Y', $timestamp ) . '-Q' . $quarter;
                case 'M':
                default:
                    $timestamp = strtotime( '-' . $depth . ' months', current_time( 'timestamp', true ) );
                    if ( false === $timestamp ) {
                        $timestamp = current_time( 'timestamp', true );
                    }
                    return gmdate( 'Y-m', $timestamp );
            }
        }

        /**
         * Map Eurostat country code to OECD country code.
         *
         * @param string $country_code Eurostat country code.
         * @return string
         */
        private function get_oecd_country_code( $country_code ) {
            return $this->get_world_bank_country_code( $country_code );
        }

        /**
         * Format one OECD value.
         *
         * @param float  $value Value.
         * @param string $mode Mode.
         * @return string
         */
        private function format_oecd_value( $value, $mode ) {
            switch ( $mode ) {
                case 'percentage':
                default:
                    return number_format_i18n( $value, 1 ) . '%';
            }
        }


        /**
         * Get IMF macro payload.
         *
         * @param bool $force Force refresh.
         * @return array<string,mixed>
         */
        public function get_imf_macro_payload( $force = false ) {
            $settings     = $this->get_settings();
            $countries    = $this->get_country_options();
            $country_code = $this->normalize_country_code( (string) $settings['country_code'] );
            $country_name = isset( $countries[ $country_code ] ) ? (string) $countries[ $country_code ] : $country_code;
            $cache_key    = self::CACHE_PREFIX . 'imf_macro_' . strtolower( sanitize_key( $country_code ) );

            if ( $force ) {
                delete_transient( $cache_key );
            }

            $cached = get_transient( $cache_key );
            if ( is_array( $cached ) ) {
                return $cached;
            }

            $imf_country = $this->get_imf_country_code( $country_code );
            if ( '' === $imf_country ) {
                $payload = array(
                    'status'         => 'unavailable',
                    'source'         => 'IMF DataMapper API',
                    'scope'          => $country_name,
                    'overview_cards' => array(),
                    'indicators'     => array(),
                    'note'           => __( 'The selected country is not mapped to a strict IMF DataMapper country code in this build.', 'noesia-smart-waiter' ),
                );
                set_transient( $cache_key, $payload, HOUR_IN_SECONDS );
                return $payload;
            }

            $indicators      = array();
            $available_cards = array();
            foreach ( $this->get_imf_macro_indicator_configs() as $config ) {
                $indicator_payload = $this->fetch_imf_macro_indicator_payload( $config, $imf_country, $country_name );
                $indicators[]      = $indicator_payload;

                if ( 'ok' === $indicator_payload['status'] && ! empty( $indicator_payload['latest']['formatted'] ) ) {
                    $available_cards[] = array(
                        'label' => (string) $config['title'],
                        'value' => (string) $indicator_payload['latest']['formatted'],
                        'meta'  => (string) $indicator_payload['latest']['period_label'],
                    );
                }
            }

            $payload = array(
                'status'         => 'ok',
                'source'         => 'IMF DataMapper API (WEO selected indicators)',
                'scope'          => $country_name,
                'overview_cards' => array_slice( $available_cards, 0, 6 ),
                'indicators'     => $indicators,
                'note'           => __( 'IMF Macro uses only fixed IMF DataMapper WEO indicator codes. No alternative providers, inferred substitutions, or mixed-source macro boxes are used.', 'noesia-smart-waiter' ),
            );

            set_transient( $cache_key, $payload, HOUR_IN_SECONDS * max( 1, absint( $settings['refresh_hours'] ) ) );
            return $payload;
        }

        /**
         * Get strict IMF macro indicator configs.
         *
         * @return array<int,array<string,string>>
         */
        private function get_imf_macro_indicator_configs() {
            return array(
                array(
                    'code'       => 'NGDP_RPCH',
                    'title'      => __( 'Real GDP growth', 'noesia-smart-waiter' ),
                    'definition' => __( 'Gross domestic product, constant prices, annual percent change.', 'noesia-smart-waiter' ),
                    'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'PCPIPCH',
                    'title'      => __( 'Inflation rate, average consumer prices', 'noesia-smart-waiter' ),
                    'definition' => __( 'Average consumer price inflation, annual percent change.', 'noesia-smart-waiter' ),
                    'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'LUR',
                    'title'      => __( 'Unemployment rate', 'noesia-smart-waiter' ),
                    'definition' => __( 'Unemployment rate as a percent of total labor force.', 'noesia-smart-waiter' ),
                    'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'BCA_NGDPD',
                    'title'      => __( 'Current account balance', 'noesia-smart-waiter' ),
                    'definition' => __( 'Current account balance as a percent of GDP.', 'noesia-smart-waiter' ),
                    'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'GGXWDG_NGDP',
                    'title'      => __( 'General government gross debt', 'noesia-smart-waiter' ),
                    'definition' => __( 'General government gross debt as a percent of GDP.', 'noesia-smart-waiter' ),
                    'source'     => __( 'IMF DataMapper / World Economic Outlook (WEO)', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
            );
        }

        /**
         * Fetch one IMF macro indicator payload.
         *
         * @param array<string,string> $config Indicator config.
         * @param string               $imf_country IMF country code.
         * @param string               $country_name Country label.
         * @return array<string,mixed>
         */
        private function fetch_imf_macro_indicator_payload( $config, $imf_country, $country_name ) {
            $request = $this->request_imf_datamapper_indicator( (string) $config['code'], $imf_country, $this->get_imf_macro_periods() );

            if ( 'ok' !== $request['status'] ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => 'IMF DataMapper API (WEO)',
                    'scope'      => $country_name,
                    'notice'     => isset( $request['message'] ) ? (string) $request['message'] : __( 'The official IMF macro request failed.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            $values = array();
            if ( isset( $request['data']['values'][ $config['code'] ][ $imf_country ] ) && is_array( $request['data']['values'][ $config['code'] ][ $imf_country ] ) ) {
                $values = $request['data']['values'][ $config['code'] ][ $imf_country ];
            }

            $rows = array();
            foreach ( $values as $period => $value ) {
                $period = sanitize_text_field( (string) $period );
                if ( ! preg_match( '/^\d{4}$/', $period ) || null === $value || '' === (string) $value ) {
                    continue;
                }
                $rows[] = array(
                    'period_raw'   => $period,
                    'period_label' => $period,
                    'value'        => (float) $value,
                    'formatted'    => $this->format_imf_value( (float) $value, (string) $config['mode'] ),
                );
            }

            if ( empty( $rows ) ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => 'IMF DataMapper API (WEO)',
                    'scope'      => $country_name,
                    'notice'     => __( 'The selected IMF macro indicator does not currently expose usable values for this country.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            usort(
                $rows,
                static function ( $left, $right ) {
                    return strcmp( (string) $right['period_raw'], (string) $left['period_raw'] );
                }
            );

            $rows   = array_slice( $rows, 0, 8 );
            $latest = $rows[0];

            return array(
                'status'     => 'ok',
                'title'      => (string) $config['title'],
                'code'       => (string) $config['code'],
                'definition' => (string) $config['definition'],
                'source'     => ! empty( $request['used_fallback'] ) && ! empty( $config['fallback_source'] ) ? (string) $config['fallback_source'] : (string) $config['source'],
                'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'World Bank', 'noesia-smart-waiter' ),
                'scope'      => $country_name,
                'latest'     => $latest,
                'history'    => array_reverse( $rows ),
                'notice'     => __( 'Only the fixed IMF DataMapper WEO indicator code is shown in this card.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Get strict development indicator configs.
         *
         * @return array<int,array<string,string>>
         */
        private function get_development_indicator_configs() {
            return array(
                array(
                    'code'       => 'SP.POP.TOTL',
                    'group'      => __( 'Population & Society', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population', 'noesia-smart-waiter' ),
                    'definition' => __( 'Total population based on the de facto definition of population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division and national statistical sources', 'noesia-smart-waiter' ),
                    'mode'       => 'integer',
                ),
                array(
                    'code'       => 'SP.DYN.LE00.IN',
                    'group'      => __( 'Population & Society', 'noesia-smart-waiter' ),
                    'title'      => __( 'Life expectancy', 'noesia-smart-waiter' ),
                    'definition' => __( 'Life expectancy at birth, total (years).', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division and national statistical sources', 'noesia-smart-waiter' ),
                    'mode'       => 'years',
                ),
                array(
                    'code'       => 'SP.URB.TOTL.IN.ZS',
                    'group'      => __( 'Population & Society', 'noesia-smart-waiter' ),
                    'title'      => __( 'Urban population', 'noesia-smart-waiter' ),
                    'definition' => __( 'Urban population as a share of total population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / United Nations Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'NY.GDP.MKTP.CD',
                    'group'      => __( 'Economy & Output', 'noesia-smart-waiter' ),
                    'title'      => __( 'GDP (current US$)', 'noesia-smart-waiter' ),
                    'definition' => __( 'Gross domestic product in current U.S. dollars.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / national accounts data', 'noesia-smart-waiter' ),
                    'mode'       => 'usd',
                ),
                array(
                    'code'       => 'NY.GDP.PCAP.CD',
                    'group'      => __( 'Economy & Output', 'noesia-smart-waiter' ),
                    'title'      => __( 'GDP per capita (current US$)', 'noesia-smart-waiter' ),
                    'definition' => __( 'Gross domestic product divided by midyear population, in current U.S. dollars.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / national accounts data', 'noesia-smart-waiter' ),
                    'mode'       => 'usd',
                ),
                array(
                    'code'       => 'NE.TRD.GNFS.ZS',
                    'group'      => __( 'Economy & Output', 'noesia-smart-waiter' ),
                    'title'      => __( 'Trade (% of GDP)', 'noesia-smart-waiter' ),
                    'definition' => __( 'Sum of exports and imports of goods and services measured as a share of GDP.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / national accounts data and OECD National Accounts data files', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SL.UEM.TOTL.ZS',
                    'group'      => __( 'Labor & Connectivity', 'noesia-smart-waiter' ),
                    'title'      => __( 'Unemployment', 'noesia-smart-waiter' ),
                    'definition' => __( 'Unemployment as a share of the total labor force (modeled ILO estimate).', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / ILOSTAT modeled estimates', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'IT.NET.USER.ZS',
                    'group'      => __( 'Labor & Connectivity', 'noesia-smart-waiter' ),
                    'title'      => __( 'Internet users', 'noesia-smart-waiter' ),
                    'definition' => __( 'Individuals who used the Internet in the last 3 months, as a share of population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / ITU', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'EG.ELC.ACCS.ZS',
                    'group'      => __( 'Labor & Connectivity', 'noesia-smart-waiter' ),
                    'title'      => __( 'Access to electricity', 'noesia-smart-waiter' ),
                    'definition' => __( 'Population with access to electricity as a share of total population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / SDG Global Database and national household surveys', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'EN.GHG.CO2.PC.CE.AR5',
                    'group'      => __( 'Environment & Infrastructure', 'noesia-smart-waiter' ),
                    'title'      => __( 'CO2 emissions excluding LULUCF per capita', 'noesia-smart-waiter' ),
                    'definition' => __( 'Carbon dioxide emissions excluding land use, land-use change and forestry, measured in metric tons of CO2 equivalent per person.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / EDGAR and IEA via World Bank Data', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
            );
        }



        /**
         * Get digital and connectivity indicator configs.
         *
         * @return array<int,array<string,mixed>>
         */
        private function get_digital_indicator_configs() {
            return array(
                array(
                    'code'       => 'IT.NET.USER.ZS',
                    'group'      => __( 'Access & Usage', 'noesia-smart-waiter' ),
                    'title'      => __( 'Internet users', 'noesia-smart-waiter' ),
                    'definition' => __( 'Individuals who used the Internet in the last 3 months, as a share of population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU) DataHub', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'IT.CEL.SETS.P2',
                    'group'      => __( 'Access & Usage', 'noesia-smart-waiter' ),
                    'title'      => __( 'Mobile cellular subscriptions', 'noesia-smart-waiter' ),
                    'definition' => __( 'Mobile cellular subscriptions per 100 people.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU) DataHub', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
                array(
                    'code'       => 'IT.NET.BBND.P2',
                    'group'      => __( 'Networks & Infrastructure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Fixed broadband subscriptions', 'noesia-smart-waiter' ),
                    'definition' => __( 'Fixed broadband subscriptions per 100 people.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU) DataHub', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
                array(
                    'code'       => 'IT.NET.SECR.P6',
                    'group'      => __( 'Networks & Infrastructure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Secure Internet servers', 'noesia-smart-waiter' ),
                    'definition' => __( 'Secure Internet servers per 1 million people.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / Netcraft Secure Server Survey and World Bank population estimates', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
                array(
                    'code'       => 'IT.NET.USER.FE.ZS',
                    'group'      => __( 'Digital Inclusion', 'noesia-smart-waiter' ),
                    'title'      => __( 'Internet users, female', 'noesia-smart-waiter' ),
                    'definition' => __( 'Female individuals who used the Internet in the last 3 months, as a share of female population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU) DataHub', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'IT.NET.USER.MA.ZS',
                    'group'      => __( 'Digital Inclusion', 'noesia-smart-waiter' ),
                    'title'      => __( 'Internet users, male', 'noesia-smart-waiter' ),
                    'definition' => __( 'Male individuals who used the Internet in the last 3 months, as a share of male population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / International Telecommunication Union (ITU) DataHub', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
            );
        }



        /**
         * Get population and society indicator configs.
         *
         * @return array<int,array<string,mixed>>
         */
        private function get_population_indicator_configs() {
            return array(
                array(
                    'code'       => 'SP.POP.GROW',
                    'group'      => __( 'Population Change', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population growth', 'noesia-smart-waiter' ),
                    'definition' => __( 'Annual population growth rate as reported by the World Bank WDI series.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division and national statistical offices', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.POP.0014.TO.ZS',
                    'group'      => __( 'Age Structure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population ages 0-14', 'noesia-smart-waiter' ),
                    'definition' => __( 'Share of the population aged 0 to 14 years.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.POP.1564.TO.ZS',
                    'group'      => __( 'Age Structure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population ages 15-64', 'noesia-smart-waiter' ),
                    'definition' => __( 'Share of the population aged 15 to 64 years.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.POP.65UP.TO.ZS',
                    'group'      => __( 'Age Structure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population ages 65 and above', 'noesia-smart-waiter' ),
                    'definition' => __( 'Share of the population aged 65 years and above.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.POP.DPND',
                    'group'      => __( 'Age Structure', 'noesia-smart-waiter' ),
                    'title'      => __( 'Age dependency ratio', 'noesia-smart-waiter' ),
                    'definition' => __( 'Dependents younger than 15 or older than 64 as a share of working-age population 15-64.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.DYN.TFRT.IN',
                    'group'      => __( 'Births & Fertility', 'noesia-smart-waiter' ),
                    'title'      => __( 'Fertility rate, total', 'noesia-smart-waiter' ),
                    'definition' => __( 'Total fertility rate, expressed as births per woman.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division, Eurostat, and national statistical offices', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
                array(
                    'code'       => 'SP.DYN.CBRT.IN',
                    'group'      => __( 'Births & Fertility', 'noesia-smart-waiter' ),
                    'title'      => __( 'Birth rate, crude', 'noesia-smart-waiter' ),
                    'definition' => __( 'Number of live births occurring during the year, per 1,000 people.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division, Eurostat, and national statistical offices', 'noesia-smart-waiter' ),
                    'mode'       => 'decimal',
                ),
                array(
                    'code'       => 'SP.POP.TOTL.FE.ZS',
                    'group'      => __( 'Gender & Mobility', 'noesia-smart-waiter' ),
                    'title'      => __( 'Population, female', 'noesia-smart-waiter' ),
                    'definition' => __( 'Female population as a share of total population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SM.POP.TOTL.ZS',
                    'group'      => __( 'Gender & Mobility', 'noesia-smart-waiter' ),
                    'title'      => __( 'International migrant stock', 'noesia-smart-waiter' ),
                    'definition' => __( 'International migrant stock as a share of total population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
                array(
                    'code'       => 'SP.RUR.TOTL.ZS',
                    'group'      => __( 'Settlement Patterns', 'noesia-smart-waiter' ),
                    'title'      => __( 'Rural population', 'noesia-smart-waiter' ),
                    'definition' => __( 'Rural population as a share of total population.', 'noesia-smart-waiter' ),
                    'source'     => __( 'World Bank WDI / UN Population Division and World Bank staff estimates', 'noesia-smart-waiter' ),
                    'mode'       => 'percentage',
                ),
            );
        }


        /**
         * Get health indicator configs.
         *
         * @return array<int,array<string,mixed>>
         */
        private function get_health_indicator_configs() {
            return array(
                array(
                    'lookup_name'         => 'UHC Service Coverage Index (SDG 3.8.1)',
                    'lookup_terms'        => array( 'UHC Service Coverage Index', 'uhc index of service coverage' ),
                    'title'               => __( 'UHC service coverage index', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Coverage of essential health services on a 0 to 100 index scale.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Universal health coverage', 'noesia-smart-waiter' ),
                    'mode'                => 'index',
                ),
                array(
                    'lookup_name'         => 'Maternal mortality ratio (per 100 000 live births)',
                    'lookup_terms'        => array( 'Maternal mortality ratio' ),
                    'title'               => __( 'Maternal mortality ratio', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Number of maternal deaths per 100,000 live births.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Mortality and child health', 'noesia-smart-waiter' ),
                    'mode'                => 'rate_100000',
                ),
                array(
                    'lookup_name'         => 'under-five mortality rate (per 1000 live births) (SDG 3.2.1)',
                    'lookup_terms'        => array( 'under-five mortality rate', 'probability of dying by age 5 per 1000 live births' ),
                    'explicit_code'       => 'MDG_0000000007',
                    'title'               => __( 'Under-five mortality rate', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Probability of dying by age five, expressed per 1,000 live births.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Mortality and child health', 'noesia-smart-waiter' ),
                    'mode'                => 'rate_1000',
                    'dimension_filter'    => "(Dim1 eq null or Dim1 eq 'BTSX')",
                    'preferred_dim_codes' => array( 'BTSX' ),
                ),
                array(
                    'lookup_name'         => 'Child mortality in neonates, neonatal mortality rate (0 to 27 days) per 1000 live births) (SDG 3.2.2)',
                    'lookup_terms'        => array( 'neonatal mortality rate', 'Child mortality in neonates' ),
                    'explicit_code'       => 'WHOSIS_000003',
                    'title'               => __( 'Neonatal mortality rate', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Deaths during the first 28 completed days of life per 1,000 live births.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Mortality and child health', 'noesia-smart-waiter' ),
                    'mode'                => 'rate_1000',
                    'dimension_filter'    => "(Dim1 eq null or Dim1 eq 'BTSX')",
                    'preferred_dim_codes' => array( 'BTSX' ),
                ),
                array(
                    'lookup_name'         => 'Current health expenditure (CHE) as percentage of gross domestic product (GDP) (%)',
                    'lookup_terms'        => array( 'Current health expenditure (CHE) as percentage of gross domestic product', 'CHE%GDP' ),
                    'title'               => __( 'Current health expenditure as % of GDP', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Current health expenditure as a percentage of gross domestic product.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Health expenditure', 'noesia-smart-waiter' ),
                    'mode'                => 'percentage',
                ),
                array(
                    'lookup_name'         => 'Domestic general government health expenditure (GGHE-D) per capita in US$',
                    'lookup_terms'        => array( 'Domestic general government health expenditure (GGHE-D) per capita in US$', 'GGHE-D_pc_US$' ),
                    'title'               => __( 'Government health expenditure per capita', 'noesia-smart-waiter' ),
                    'definition'          => __( 'Domestic general government current health expenditure per capita in US dollars.', 'noesia-smart-waiter' ),
                    'source'              => __( 'WHO Global Health Observatory (GHO OData API)', 'noesia-smart-waiter' ),
                    'group'               => __( 'Health expenditure', 'noesia-smart-waiter' ),
                    'mode'                => 'usd',
                ),
            );
        }

        /**
         * Fetch one WHO health indicator payload.
         *
         * @param array<string,mixed> $config Indicator config.
         * @param string              $who_country WHO country code.
         * @param string              $country_name Country name.
         * @return array<string,mixed>
         */
        private function fetch_who_health_indicator_payload( $config, $who_country, $country_name ) {
            $resolved = $this->resolve_who_indicator_code( $config );

            if ( 'ok' !== $resolved['status'] || empty( $resolved['code'] ) ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => '',
                    'definition' => (string) $config['definition'],
                    'source'     => (string) $config['source'],
                    'group'      => (string) $config['group'],
                    'scope'      => $country_name,
                    'notice'     => isset( $resolved['message'] ) ? (string) $resolved['message'] : __( 'The official WHO indicator code could not be resolved safely.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            $request = $this->request_who_indicator_data( (string) $resolved['code'], $who_country, $config );
            if ( 'ok' !== $request['status'] ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $resolved['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => (string) $config['source'],
                    'group'      => (string) $config['group'],
                    'scope'      => $country_name,
                    'notice'     => isset( $request['message'] ) ? (string) $request['message'] : __( 'The official WHO indicator request failed.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            $rows = $this->extract_who_indicator_rows( (array) $request['data'], $who_country, $config );
            if ( empty( $rows ) ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $resolved['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => (string) $config['source'],
                    'group'      => (string) $config['group'],
                    'scope'      => $country_name,
                    'notice'     => __( 'The selected official WHO indicator does not currently expose a usable country-level value for this country.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            usort(
                $rows,
                static function ( $left, $right ) {
                    if ( (int) $left['preferred_rank'] !== (int) $right['preferred_rank'] ) {
                        return (int) $left['preferred_rank'] - (int) $right['preferred_rank'];
                    }
                    if ( (int) $left['dimension_count'] !== (int) $right['dimension_count'] ) {
                        return (int) $left['dimension_count'] - (int) $right['dimension_count'];
                    }
                    return strcmp( (string) $right['period_sort'], (string) $left['period_sort'] );
                }
            );

            $deduped = array();
            foreach ( $rows as $row ) {
                $key = (string) $row['period_raw'];
                if ( ! isset( $deduped[ $key ] ) ) {
                    $deduped[ $key ] = $row;
                }
            }

            $rows = array_values( $deduped );
            usort(
                $rows,
                static function ( $left, $right ) {
                    return strcmp( (string) $right['period_sort'], (string) $left['period_sort'] );
                }
            );

            $rows   = array_slice( $rows, 0, 6 );
            $latest = $rows[0];

            return array(
                'status'     => 'ok',
                'title'      => (string) $config['title'],
                'code'       => (string) $resolved['code'],
                'definition' => (string) $config['definition'],
                'source'     => (string) $config['source'],
                'group'      => (string) $config['group'],
                'scope'      => $country_name,
                'latest'     => $latest,
                'history'    => array_reverse( $rows ),
                'notice'     => __( 'Only the exact official WHO indicator match is shown in this card.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Resolve WHO indicator code.
         *
         * @param array<string,mixed> $config Indicator config.
         * @return array<string,string>
         */
        private function resolve_who_indicator_code( $config ) {
            if ( ! empty( $config['explicit_code'] ) && is_string( $config['explicit_code'] ) ) {
                return array(
                    'status' => 'ok',
                    'code'   => sanitize_text_field( $config['explicit_code'] ),
                );
            }

            $cache_key = self::CACHE_PREFIX . 'who_code_' . md5( sanitize_text_field( (string) $config['lookup_name'] ) );
            $cached    = get_transient( $cache_key );
            if ( is_string( $cached ) && '' !== $cached ) {
                return array(
                    'status' => 'ok',
                    'code'   => $cached,
                );
            }

            $candidates = array();
            $exact_name = sanitize_text_field( (string) $config['lookup_name'] );
            if ( '' !== $exact_name ) {
                $request = $this->request_who_indicator_catalog( "IndicatorName eq '" . str_replace( "'", "''", $exact_name ) . "'" );
                if ( 'ok' === $request['status'] && ! empty( $request['data'] ) ) {
                    $candidates = (array) $request['data'];
                }
            }

            if ( empty( $candidates ) && ! empty( $config['lookup_terms'] ) && is_array( $config['lookup_terms'] ) ) {
                foreach ( $config['lookup_terms'] as $term ) {
                    $term = sanitize_text_field( (string) $term );
                    if ( '' === $term ) {
                        continue;
                    }
                    $request = $this->request_who_indicator_catalog( "contains(IndicatorName,'" . str_replace( "'", "''", $term ) . "')" );
                    if ( 'ok' === $request['status'] && ! empty( $request['data'] ) ) {
                        $candidates = array_merge( $candidates, (array) $request['data'] );
                    }
                }
            }

            if ( empty( $candidates ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO indicator catalog did not return a deterministic indicator code for this metric.', 'noesia-smart-waiter' ),
                );
            }

            $aliases = array();
            $aliases[] = $this->normalize_catalog_name( (string) $config['lookup_name'] );
            if ( ! empty( $config['lookup_terms'] ) && is_array( $config['lookup_terms'] ) ) {
                foreach ( $config['lookup_terms'] as $alias ) {
                    $aliases[] = $this->normalize_catalog_name( (string) $alias );
                }
            }
            $aliases = array_filter( array_unique( $aliases ) );

            $seen    = array();
            $matches = array();
            foreach ( $candidates as $candidate ) {
                if ( ! is_array( $candidate ) ) {
                    continue;
                }
                $code = isset( $candidate['IndicatorCode'] ) ? sanitize_text_field( (string) $candidate['IndicatorCode'] ) : '';
                $name = isset( $candidate['IndicatorName'] ) ? sanitize_text_field( (string) $candidate['IndicatorName'] ) : '';
                if ( '' === $code || '' === $name || isset( $seen[ $code ] ) ) {
                    continue;
                }
                $seen[ $code ] = true;
                $normalized_name = $this->normalize_catalog_name( $name );
                $is_match        = in_array( $normalized_name, $aliases, true );
                if ( ! $is_match ) {
                    foreach ( $aliases as $alias ) {
                        if ( '' !== $alias && false !== strpos( $normalized_name, $alias ) ) {
                            $is_match = true;
                            break;
                        }
                    }
                }
                if ( $is_match ) {
                    $matches[] = array(
                        'code' => $code,
                        'name' => $name,
                    );
                }
            }

            if ( 1 !== count( $matches ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO indicator catalog returned an ambiguous match for this metric, so the card is hidden.', 'noesia-smart-waiter' ),
                );
            }

            set_transient( $cache_key, $matches[0]['code'], WEEK_IN_SECONDS );
            return array(
                'status' => 'ok',
                'code'   => $matches[0]['code'],
            );
        }

        /**
         * Request WHO indicator catalog.
         *
         * @param string $filter_expression Filter expression.
         * @return array<string,mixed>
         */
        private function request_who_indicator_catalog( $filter_expression ) {
            $url = add_query_arg(
                array(
                    '$format' => 'json',
                    '$filter' => (string) $filter_expression,
                ),
                'https://ghoapi.azureedge.net/api/Indicator'
            );

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 15,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'ghoapi.azureedge.net', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = $this->normalize_json_response_body( wp_remote_retrieve_body( $response ) );
            $data = json_decode( $body, true );

            if ( $code >= 400 ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO GHO indicator catalog returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }

            if ( ! is_array( $data ) || ! isset( $data['value'] ) || ! is_array( $data['value'] ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO GHO indicator catalog returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data['value'],
            );
        }

        /**
         * Request WHO indicator data.
         *
         * @param string              $indicator_code Indicator code.
         * @param string              $who_country WHO country code.
         * @param array<string,mixed> $config Indicator config.
         * @return array<string,mixed>
         */
        private function request_who_indicator_data( $indicator_code, $who_country, $config ) {
            $filter_parts = array(
                "SpatialDim eq '" . str_replace( "'", "''", sanitize_text_field( $who_country ) ) . "'",
            );

            if ( ! empty( $config['dimension_filter'] ) ) {
                $filter_parts[] = (string) $config['dimension_filter'];
            }

            $url = add_query_arg(
                array(
                    '$format' => 'json',
                    '$filter' => implode( ' and ', $filter_parts ),
                ),
                'https://ghoapi.azureedge.net/api/' . rawurlencode( sanitize_text_field( (string) $indicator_code ) )
            );

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 18,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'ghoapi.azureedge.net', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = $this->normalize_json_response_body( wp_remote_retrieve_body( $response ) );
            $data = json_decode( $body, true );

            if ( $code >= 400 ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO GHO returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }

            if ( ! is_array( $data ) || ! isset( $data['value'] ) || ! is_array( $data['value'] ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'WHO GHO returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data['value'],
            );
        }

        /**
         * Extract WHO indicator rows.
         *
         * @param array<int,mixed>    $rows Raw rows.
         * @param string              $who_country WHO country code.
         * @param array<string,mixed> $config Indicator config.
         * @return array<int,array<string,mixed>>
         */
        private function extract_who_indicator_rows( $rows, $who_country, $config ) {
            $prepared            = array();
            $preferred_dim_codes = isset( $config['preferred_dim_codes'] ) && is_array( $config['preferred_dim_codes'] ) ? array_map( 'sanitize_text_field', $config['preferred_dim_codes'] ) : array();

            foreach ( $rows as $row ) {
                if ( ! is_array( $row ) ) {
                    continue;
                }

                $spatial_dim = isset( $row['SpatialDim'] ) ? sanitize_text_field( (string) $row['SpatialDim'] ) : '';
                if ( '' !== $spatial_dim && $spatial_dim !== $who_country ) {
                    continue;
                }

                $value = $this->extract_who_numeric_value( $row );
                if ( null === $value ) {
                    continue;
                }

                $period_raw = $this->extract_who_period_value( $row );
                if ( '' === $period_raw ) {
                    continue;
                }

                $dimension_count = 0;
                $preferred_rank  = empty( $preferred_dim_codes ) ? 0 : 1;
                for ( $index = 1; $index <= 8; $index++ ) {
                    $key = 'Dim' . $index;
                    if ( ! isset( $row[ $key ] ) || '' === trim( (string) $row[ $key ] ) ) {
                        continue;
                    }
                    $dimension_count++;
                    $candidate = sanitize_text_field( (string) $row[ $key ] );
                    if ( in_array( $candidate, $preferred_dim_codes, true ) ) {
                        $preferred_rank = 0;
                    }
                }

                $prepared[] = array(
                    'period_raw'      => $period_raw,
                    'period_label'    => $this->format_who_period_label( $period_raw ),
                    'period_sort'     => $this->sortable_period_key( $period_raw ),
                    'value'           => $value,
                    'formatted'       => $this->format_health_value( $value, (string) $config['mode'] ),
                    'dimension_count' => $dimension_count,
                    'preferred_rank'  => $preferred_rank,
                );
            }

            return $prepared;
        }

        /**
         * Extract one numeric WHO value.
         *
         * @param array<string,mixed> $row Row.
         * @return float|null
         */
        private function extract_who_numeric_value( $row ) {
            $candidates = array( 'NumericValue', 'Value', 'DisplayValue' );

            foreach ( $candidates as $key ) {
                if ( ! isset( $row[ $key ] ) ) {
                    continue;
                }
                $raw = trim( (string) $row[ $key ] );
                if ( '' === $raw ) {
                    continue;
                }
                $normalized = str_replace( array( ',', '%', ' ' ), array( '', '', '' ), $raw );
                if ( is_numeric( $normalized ) ) {
                    return (float) $normalized;
                }
            }

            return null;
        }

        /**
         * Extract WHO period value.
         *
         * @param array<string,mixed> $row Row.
         * @return string
         */
        private function extract_who_period_value( $row ) {
            foreach ( array( 'TimeDimensionValue', 'TimeDim', 'Year' ) as $key ) {
                if ( empty( $row[ $key ] ) ) {
                    continue;
                }
                $value = sanitize_text_field( (string) $row[ $key ] );
                if ( preg_match( '/^\d{4}$/', $value ) ) {
                    return $value;
                }
                if ( preg_match( '/^(\d{4})-\d{2}-\d{2}$/', $value, $matches ) ) {
                    return $matches[1];
                }
            }

            foreach ( array( 'TimeDimensionEnd', 'TimeDimensionBegin' ) as $key ) {
                if ( empty( $row[ $key ] ) ) {
                    continue;
                }
                $value = sanitize_text_field( (string) $row[ $key ] );
                if ( preg_match( '/^(\d{4})-\d{2}-\d{2}/', $value, $matches ) ) {
                    return $matches[1];
                }
            }

            return '';
        }

        /**
         * Format WHO period label.
         *
         * @param string $period_raw Period.
         * @return string
         */
        private function format_who_period_label( $period_raw ) {
            return sanitize_text_field( (string) $period_raw );
        }

        /**
         * Normalize catalog name.
         *
         * @param string $value Name.
         * @return string
         */
        private function normalize_catalog_name( $value ) {
            $value = strtolower( sanitize_text_field( $value ) );
            $value = preg_replace( '/[^a-z0-9]+/', ' ', $value );
            return trim( preg_replace( '/\s+/', ' ', $value ) );
        }

        /**
         * Get WHO country code.
         *
         * @param string $country_code Country code.
         * @return string
         */
        private function get_who_country_code( $country_code ) {
            return $this->get_world_bank_country_code( $country_code );
        }

        /**
         * Format one health value.
         *
         * @param float  $value Value.
         * @param string $mode Mode.
         * @return string
         */
        private function format_health_value( $value, $mode ) {
            switch ( $mode ) {
                case 'usd':
                    if ( abs( $value ) >= 1000000000000 ) {
                        return '$' . number_format_i18n( $value / 1000000000000, 2 ) . 'T';
                    }
                    if ( abs( $value ) >= 1000000000 ) {
                        return '$' . number_format_i18n( $value / 1000000000, 2 ) . 'B';
                    }
                    if ( abs( $value ) >= 1000000 ) {
                        return '$' . number_format_i18n( $value / 1000000, 2 ) . 'M';
                    }
                    return '$' . number_format_i18n( $value, 0 );
                case 'percentage':
                    return number_format_i18n( $value, 1 ) . '%';
                case 'index':
                case 'rate_100000':
                case 'rate_1000':
                default:
                    return number_format_i18n( $value, 1 );
            }
        }
        /**
         * Fetch one World Bank indicator payload.
         *
         * @param array<string,string> $config Indicator config.
         * @param string               $wb_country World Bank country code.
         * @param string               $country_name Country label.
         * @return array<string,mixed>
         */
        private function fetch_world_bank_indicator_payload( $config, $wb_country, $country_name ) {
            $request = $this->request_world_bank_indicator( (string) $config['code'], $wb_country );

            if ( 'ok' !== $request['status'] ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => 'World Bank Indicators API (WDI)',
                    'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'World Bank', 'noesia-smart-waiter' ),
                    'scope'      => $country_name,
                    'notice'     => isset( $request['message'] ) ? (string) $request['message'] : __( 'The official World Bank indicator request failed.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            $rows = array();
            foreach ( (array) $request['data'] as $row ) {
                if ( ! is_array( $row ) || null === ( $row['value'] ?? null ) || '' === (string) ( $row['date'] ?? '' ) ) {
                    continue;
                }

                $value = (float) $row['value'];
                $year  = sanitize_text_field( (string) $row['date'] );
                $rows[] = array(
                    'period_raw'   => $year,
                    'period_label' => $year,
                    'value'        => $value,
                    'formatted'    => $this->format_development_value( $value, (string) $config['mode'] ),
                );
            }

            if ( empty( $rows ) ) {
                return array(
                    'status'     => 'unavailable',
                    'title'      => (string) $config['title'],
                    'code'       => (string) $config['code'],
                    'definition' => (string) $config['definition'],
                    'source'     => 'World Bank Indicators API (WDI)',
                    'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'World Bank', 'noesia-smart-waiter' ),
                    'scope'      => $country_name,
                    'notice'     => __( 'The selected official indicator does not currently expose usable values for this country.', 'noesia-smart-waiter' ),
                    'latest'     => array(),
                    'history'    => array(),
                );
            }

            usort(
                $rows,
                static function ( $left, $right ) {
                    return strcmp( (string) $right['period_raw'], (string) $left['period_raw'] );
                }
            );

            $rows = array_slice( $rows, 0, 6 );
            $latest = $rows[0];

            return array(
                'status'     => 'ok',
                'title'      => (string) $config['title'],
                'code'       => (string) $config['code'],
                'definition' => (string) $config['definition'],
                'source'     => ! empty( $request['used_fallback'] ) && ! empty( $config['fallback_source'] ) ? (string) $config['fallback_source'] : (string) $config['source'],
                'group'      => isset( $config['group'] ) ? (string) $config['group'] : __( 'World Bank', 'noesia-smart-waiter' ),
                'scope'      => $country_name,
                'latest'     => $latest,
                'history'    => array_reverse( $rows ),
                'notice'     => __( 'Only the fixed official WDI indicator code is shown in this card.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Request one World Bank indicator series.
         *
         * @param string $indicator_code Indicator code.
         * @param string $wb_country World Bank country code.
         * @return array<string,mixed>
         */
        private function request_world_bank_indicator( $indicator_code, $wb_country ) {
            $url = add_query_arg(
                array(
                    'format'   => 'json',
                    'source'   => 2,
                    'per_page' => 80,
                    'mrv'      => 10,
                ),
                'https://api.worldbank.org/v2/country/' . rawurlencode( sanitize_text_field( $wb_country ) ) . '/indicator/' . rawurlencode( sanitize_text_field( $indicator_code ) )
            );

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 15,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'api.worldbank.org', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = $this->normalize_json_response_body( wp_remote_retrieve_body( $response ) );
            $data = json_decode( $body, true );

            if ( $code >= 400 ) {
                $message = __( 'World Bank returned an HTTP error.', 'noesia-smart-waiter' );
                if ( is_array( $data ) && isset( $data[0]['message'][0]['value'] ) ) {
                    $message = sanitize_text_field( (string) $data[0]['message'][0]['value'] );
                }
                return array(
                    'status'  => 'unavailable',
                    'message' => $message,
                );
            }

            if ( ! is_array( $data ) || ! isset( $data[1] ) || ! is_array( $data[1] ) ) {
                if ( is_array( $data ) && isset( $data[0]['message'][0]['value'] ) ) {
                    return array(
                        'status'  => 'unavailable',
                        'message' => sanitize_text_field( (string) $data[0]['message'][0]['value'] ),
                    );
                }
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'World Bank returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data[1],
            );
        }


        /**
         * Request one IMF DataMapper indicator series.
         *
         * @param string   $indicator_code Indicator code.
         * @param string   $imf_country IMF country code.
         * @param string[] $periods Requested periods.
         * @return array<string,mixed>
         */

        /**
         * Normalize remote JSON body before decoding.
         *
         * @param string $body Raw body.
         * @return string
         */
        private function normalize_json_response_body( $body ) {
            $body = (string) $body;
            if ( '' === $body ) {
                return $body;
            }

            if ( 0 === strpos( $body, "ï»¿" ) ) {
                $body = substr( $body, 3 );
            }

            return trim( $body );
        }

        private function request_imf_datamapper_indicator( $indicator_code, $imf_country, $periods ) {
            $periods = array_filter(
                array_map(
                    static function ( $period ) {
                        $period = sanitize_text_field( (string) $period );
                        return preg_match( '/^\d{4}$/', $period ) ? $period : '';
                    },
                    is_array( $periods ) ? $periods : array()
                )
            );

            $url = 'https://www.imf.org/external/datamapper/api/v1/' . rawurlencode( sanitize_text_field( $indicator_code ) ) . '/' . rawurlencode( sanitize_text_field( $imf_country ) );
            if ( ! empty( $periods ) ) {
                $url = add_query_arg(
                    array(
                        'periods' => implode( ',', $periods ),
                    ),
                    $url
                );
            }

            $response = wp_remote_get(
                $url,
                array(
                    'timeout' => 15,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'www.imf.org', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            $data = json_decode( $body, true );

            if ( $code >= 400 ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'IMF DataMapper returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }

            if ( ! is_array( $data ) || ! isset( $data['values'] ) || ! is_array( $data['values'] ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'IMF DataMapper returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data,
            );
        }

        /**
         * Get IMF macro periods.
         *
         * @return string[]
         */
        private function get_imf_macro_periods() {
            $current_year = (int) gmdate( 'Y' );
            $periods      = array();
            for ( $year = $current_year - 6; $year <= $current_year + 4; $year++ ) {
                $periods[] = (string) $year;
            }
            return $periods;
        }

        /**
         * Map Eurostat country code to IMF country code.
         *
         * @param string $country_code Eurostat country code.
         * @return string
         */
        private function get_imf_country_code( $country_code ) {
            $map = array(
                'AL' => 'ALB',
                'AT' => 'AUT',
                'BA' => 'BIH',
                'BE' => 'BEL',
                'BG' => 'BGR',
                'CH' => 'CHE',
                'CY' => 'CYP',
                'CZ' => 'CZE',
                'DE' => 'DEU',
                'DK' => 'DNK',
                'EE' => 'EST',
                'EL' => 'GRC',
                'ES' => 'ESP',
                'FI' => 'FIN',
                'FR' => 'FRA',
                'HR' => 'HRV',
                'HU' => 'HUN',
                'IE' => 'IRL',
                'IS' => 'ISL',
                'IT' => 'ITA',
                'LI' => 'LIE',
                'LT' => 'LTU',
                'LU' => 'LUX',
                'LV' => 'LVA',
                'ME' => 'MNE',
                'MK' => 'MKD',
                'MT' => 'MLT',
                'NL' => 'NLD',
                'NO' => 'NOR',
                'PL' => 'POL',
                'PT' => 'PRT',
                'RO' => 'ROU',
                'RS' => 'SRB',
                'SE' => 'SWE',
                'SI' => 'SVN',
                'SK' => 'SVK',
                'TR' => 'TUR',
            );

            $country_code = $this->normalize_country_code( $country_code );
            return isset( $map[ $country_code ] ) ? $map[ $country_code ] : '';
        }

        /**
         * Format one IMF value.
         *
         * @param float  $value Value.
         * @param string $mode Mode.
         * @return string
         */
        private function format_imf_value( $value, $mode ) {
            switch ( $mode ) {
                case 'percentage':
                default:
                    return number_format_i18n( $value, 1 ) . '%';
            }
        }

        /**
         * Map Eurostat country code to World Bank country code.
         *
         * @param string $country_code Eurostat country code.
         * @return string
         */
        private function get_world_bank_country_code( $country_code ) {
            $map = array(
                'AL' => 'ALB',
                'AT' => 'AUT',
                'BA' => 'BIH',
                'BE' => 'BEL',
                'BG' => 'BGR',
                'CH' => 'CHE',
                'CY' => 'CYP',
                'CZ' => 'CZE',
                'DE' => 'DEU',
                'DK' => 'DNK',
                'EE' => 'EST',
                'EL' => 'GRC',
                'ES' => 'ESP',
                'FI' => 'FIN',
                'FR' => 'FRA',
                'HR' => 'HRV',
                'HU' => 'HUN',
                'IE' => 'IRL',
                'IS' => 'ISL',
                'IT' => 'ITA',
                'LI' => 'LIE',
                'LT' => 'LTU',
                'LU' => 'LUX',
                'LV' => 'LVA',
                'ME' => 'MNE',
                'MK' => 'MKD',
                'MT' => 'MLT',
                'NL' => 'NLD',
                'NO' => 'NOR',
                'PL' => 'POL',
                'PT' => 'PRT',
                'RO' => 'ROU',
                'RS' => 'SRB',
                'SE' => 'SWE',
                'SI' => 'SVN',
                'SK' => 'SVK',
                'TR' => 'TUR',
            );

            $country_code = $this->normalize_country_code( $country_code );
            return isset( $map[ $country_code ] ) ? $map[ $country_code ] : '';
        }

        /**
         * Format one development value.
         *
         * @param float  $value Value.
         * @param string $mode Mode.
         * @return string
         */
        private function format_development_value( $value, $mode ) {
            switch ( $mode ) {
                case 'usd':
                    if ( abs( $value ) >= 1000000000000 ) {
                        return '$' . number_format_i18n( $value / 1000000000000, 2 ) . 'T';
                    }
                    if ( abs( $value ) >= 1000000000 ) {
                        return '$' . number_format_i18n( $value / 1000000000, 2 ) . 'B';
                    }
                    if ( abs( $value ) >= 1000000 ) {
                        return '$' . number_format_i18n( $value / 1000000, 2 ) . 'M';
                    }
                    return '$' . number_format_i18n( $value, 0 );
                case 'percentage':
                    return number_format_i18n( $value, 1 ) . '%';
                case 'years':
                    return number_format_i18n( $value, 1 ) . 'y';
                case 'decimal':
                    return number_format_i18n( $value, 2 );
                case 'integer':
                default:
                    return number_format_i18n( $value, 0 );
            }
        }

        /**
         * Fetch NIH payload.
         *
         * @param string $country_code Country code.
         * @param string $country_name Country name.
         * @return array<string,mixed>
         */
        private function fetch_nih_payload( $country_code, $country_name ) {
            $nih_country = $this->get_nih_country_name( $country_code, $country_name );
            if ( '' === $nih_country ) {
                return array(
                    'overview_cards' => array(),
                    'projects'       => array(),
                    'institutes'     => array(),
                    'organizations'  => array(),
                    'note'           => __( 'NIH RePORTER country matching is not configured for this country yet, so the research tab stays hidden rather than guessing.', 'noesia-smart-waiter' ),
                    'notice'         => __( 'Only NIH RePORTER project records matched by grantee organization country and recent fiscal years are shown in this tab.', 'noesia-smart-waiter' ),
                );
            }

            $fiscal_years = $this->get_nih_fiscal_years();
            $request      = $this->request_nih_reporter_projects(
                array(
                    'org_countries' => array( $nih_country ),
                    'fiscal_years'  => $fiscal_years,
                ),
                12,
                0
            );

            if ( 'ok' !== $request['status'] ) {
                return array(
                    'overview_cards' => array(
                        array(
                            'label' => __( 'Country Scope', 'noesia-smart-waiter' ),
                            'value' => $country_name,
                            'meta'  => __( 'NIH RePORTER organization country filter', 'noesia-smart-waiter' ),
                        ),
                        array(
                            'label' => __( 'Fiscal window', 'noesia-smart-waiter' ),
                            'value' => $this->format_nih_fiscal_window_label( $fiscal_years ),
                            'meta'  => __( 'Recent NIH fiscal years', 'noesia-smart-waiter' ),
                        ),
                    ),
                    'projects'      => array(),
                    'institutes'    => array(),
                    'organizations' => array(),
                    'note'          => isset( $request['message'] ) ? (string) $request['message'] : __( 'NIH RePORTER is currently unavailable for this request.', 'noesia-smart-waiter' ),
                    'notice'        => __( 'Only NIH RePORTER project records matched by grantee organization country and recent fiscal years are shown in this tab.', 'noesia-smart-waiter' ),
                );
            }

            $data     = isset( $request['data'] ) && is_array( $request['data'] ) ? $request['data'] : array();
            $meta     = isset( $data['meta'] ) && is_array( $data['meta'] ) ? $data['meta'] : array();
            $results  = isset( $data['results'] ) && is_array( $data['results'] ) ? $data['results'] : array();
            $projects = array();
            $institutes_counter = array();
            $organizations_counter = array();
            $latest_fy = '';

            foreach ( $results as $row ) {
                if ( ! is_array( $row ) ) {
                    continue;
                }

                $project_title = isset( $row['project_title'] ) ? sanitize_text_field( (string) $row['project_title'] ) : '';
                $project_num   = isset( $row['project_num'] ) ? sanitize_text_field( (string) $row['project_num'] ) : '';
                $fiscal_year   = isset( $row['fiscal_year'] ) ? sanitize_text_field( (string) $row['fiscal_year'] ) : '';
                $award_amount  = isset( $row['award_amount'] ) && is_numeric( $row['award_amount'] ) ? (float) $row['award_amount'] : null;
                $detail_url    = isset( $row['project_detail_url'] ) ? esc_url_raw( (string) $row['project_detail_url'] ) : '';
                $organization  = '';
                $admin_ic      = '';
                $lead_pi       = '';

                if ( isset( $row['organization'] ) && is_array( $row['organization'] ) ) {
                    $organization = isset( $row['organization']['org_name'] ) ? sanitize_text_field( (string) $row['organization']['org_name'] ) : '';
                }
                if ( isset( $row['agency_ic_admin'] ) && is_array( $row['agency_ic_admin'] ) ) {
                    $admin_ic = isset( $row['agency_ic_admin']['abbreviation'] ) ? sanitize_text_field( (string) $row['agency_ic_admin']['abbreviation'] ) : '';
                    if ( '' === $admin_ic && isset( $row['agency_ic_admin']['name'] ) ) {
                        $admin_ic = sanitize_text_field( (string) $row['agency_ic_admin']['name'] );
                    }
                }
                if ( isset( $row['principal_investigators'] ) && is_array( $row['principal_investigators'] ) ) {
                    foreach ( $row['principal_investigators'] as $pi ) {
                        if ( ! is_array( $pi ) || empty( $pi['full_name'] ) ) {
                            continue;
                        }
                        if ( ! empty( $pi['is_contact_pi'] ) ) {
                            $lead_pi = sanitize_text_field( (string) $pi['full_name'] );
                            break;
                        }
                        if ( '' === $lead_pi ) {
                            $lead_pi = sanitize_text_field( (string) $pi['full_name'] );
                        }
                    }
                }

                if ( '' !== $admin_ic ) {
                    if ( ! isset( $institutes_counter[ $admin_ic ] ) ) {
                        $institutes_counter[ $admin_ic ] = 0;
                    }
                    $institutes_counter[ $admin_ic ]++;
                }
                if ( '' !== $organization ) {
                    if ( ! isset( $organizations_counter[ $organization ] ) ) {
                        $organizations_counter[ $organization ] = 0;
                    }
                    $organizations_counter[ $organization ]++;
                }
                if ( '' !== $fiscal_year && ( '' === $latest_fy || strcmp( $fiscal_year, $latest_fy ) > 0 ) ) {
                    $latest_fy = $fiscal_year;
                }

                $projects[] = array(
                    'title'           => $project_title,
                    'project_num'     => $project_num,
                    'organization'    => $organization,
                    'admin_ic'        => $admin_ic,
                    'fiscal_year'     => $fiscal_year,
                    'award_formatted' => null !== $award_amount ? $this->format_nih_currency( $award_amount ) : '—',
                    'lead_pi'         => $lead_pi,
                    'detail_url'      => $detail_url,
                );
            }

            arsort( $institutes_counter );
            arsort( $organizations_counter );

            $institutes = array();
            foreach ( array_slice( $institutes_counter, 0, 8, true ) as $label => $count ) {
                $institutes[] = array(
                    'label' => $label,
                    'count' => (int) $count,
                );
            }

            $organizations = array();
            foreach ( array_slice( $organizations_counter, 0, 8, true ) as $label => $count ) {
                $organizations[] = array(
                    'label' => $label,
                    'count' => (int) $count,
                );
            }

            $total_results = isset( $meta['total'] ) ? absint( $meta['total'] ) : count( $projects );

            return array(
                'overview_cards' => array(
                    array(
                        'label' => __( 'Country Scope', 'noesia-smart-waiter' ),
                        'value' => $country_name,
                        'meta'  => __( 'NIH RePORTER organization country filter', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Fiscal window', 'noesia-smart-waiter' ),
                        'value' => $this->format_nih_fiscal_window_label( $fiscal_years ),
                        'meta'  => __( 'Recent NIH fiscal years', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Matched projects', 'noesia-smart-waiter' ),
                        'value' => number_format_i18n( $total_results ),
                        'meta'  => __( 'Total strict matches in NIH RePORTER', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Returned deck', 'noesia-smart-waiter' ),
                        'value' => number_format_i18n( count( $projects ) ),
                        'meta'  => __( 'Recent records shown in this build', 'noesia-smart-waiter' ),
                    ),
                    array(
                        'label' => __( 'Latest fiscal year', 'noesia-smart-waiter' ),
                        'value' => '' !== $latest_fy ? $latest_fy : '—',
                        'meta'  => __( 'Latest FY visible in returned records', 'noesia-smart-waiter' ),
                    ),
                ),
                'projects'      => $projects,
                'institutes'    => $institutes,
                'organizations' => $organizations,
                'project_note'  => __( 'Recent project records are sorted by project start date using the fixed NIH RePORTER Project API search and include only the selected country filter plus the recent fiscal-year window.', 'noesia-smart-waiter' ),
                'note'          => __( 'This tab shows NIH-funded project records matched by grantee organization country over a fixed recent fiscal-year window. It is useful for research activity visibility, not for country health outcomes or clinical efficacy claims.', 'noesia-smart-waiter' ),
                'notice'        => __( 'Only NIH RePORTER project records matched by grantee organization country and recent fiscal years are shown in this tab.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Request NIH RePORTER project records.
         *
         * @param array<string,mixed> $criteria Search criteria.
         * @param int                 $limit Limit.
         * @param int                 $offset Offset.
         * @return array<string,mixed>
         */
        private function request_nih_reporter_projects( $criteria, $limit = 12, $offset = 0 ) {
            $body = array(
                'criteria'       => is_array( $criteria ) ? $criteria : array(),
                'include_fields' => array(
                    'ProjectTitle',
                    'ProjectNum',
                    'Organization',
                    'AgencyIcAdmin',
                    'AwardAmount',
                    'FiscalYear',
                    'PrincipalInvestigators',
                    'ProjectStartDate',
                    'ProjectEndDate',
                    'ProjectDetailUrl',
                ),
                'offset'         => max( 0, absint( $offset ) ),
                'limit'          => max( 1, min( 50, absint( $limit ) ) ),
                'sort_field'     => 'project_start_date',
                'sort_order'     => 'desc',
            );

            $response = wp_remote_post(
                'https://api.reporter.nih.gov/v2/projects/search',
                array(
                    'timeout' => 18,
                    'headers' => array(
                        'Accept'       => 'application/json',
                        'Content-Type' => 'application/json',
                    ),
                    'body'    => wp_json_encode( $body ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => $this->build_remote_error_message( 'api.reporter.nih.gov', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $data = json_decode( $this->normalize_json_response_body( wp_remote_retrieve_body( $response ) ), true );

            if ( $code >= 400 ) {
                $message = __( 'NIH RePORTER returned an HTTP error.', 'noesia-smart-waiter' );
                if ( is_array( $data ) && ! empty( $data['message'] ) ) {
                    $message = sanitize_text_field( is_array( $data['message'] ) ? wp_json_encode( $data['message'] ) : (string) $data['message'] );
                }
                return array(
                    'status'  => 'unavailable',
                    'message' => $message,
                );
            }

            if ( ! is_array( $data ) || ! isset( $data['results'] ) || ! is_array( $data['results'] ) ) {
                return array(
                    'status'  => 'unavailable',
                    'message' => __( 'NIH RePORTER returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            return array(
                'status' => 'ok',
                'data'   => $data,
            );
        }

        /**
         * Get recent NIH fiscal years.
         *
         * @return int[]
         */
        private function get_nih_fiscal_years() {
            $current_year = (int) gmdate( 'Y' );
            $years        = array();
            for ( $year = $current_year - 4; $year <= $current_year; $year++ ) {
                $years[] = $year;
            }
            return $years;
        }

        /**
         * Format NIH fiscal window.
         *
         * @param int[] $years Years.
         * @return string
         */
        private function format_nih_fiscal_window_label( $years ) {
            $years = array_values( array_filter( array_map( 'absint', is_array( $years ) ? $years : array() ) ) );
            if ( empty( $years ) ) {
                return '—';
            }
            return 'FY ' . $years[0] . '–' . $years[ count( $years ) - 1 ];
        }

        /**
         * Map app country to NIH org country.
         *
         * @param string $country_code Country code.
         * @param string $country_name Country name.
         * @return string
         */
        private function get_nih_country_name( $country_code, $country_name ) {
            $map = array(
                'AL' => 'ALBANIA',
                'AT' => 'AUSTRIA',
                'BA' => 'BOSNIA AND HERZEGOVINA',
                'BE' => 'BELGIUM',
                'BG' => 'BULGARIA',
                'CH' => 'SWITZERLAND',
                'CY' => 'CYPRUS',
                'CZ' => 'CZECH REPUBLIC',
                'DE' => 'GERMANY',
                'DK' => 'DENMARK',
                'EE' => 'ESTONIA',
                'EL' => 'GREECE',
                'ES' => 'SPAIN',
                'FI' => 'FINLAND',
                'FR' => 'FRANCE',
                'HR' => 'CROATIA',
                'HU' => 'HUNGARY',
                'IE' => 'IRELAND',
                'IS' => 'ICELAND',
                'IT' => 'ITALY',
                'LI' => 'LIECHTENSTEIN',
                'LT' => 'LITHUANIA',
                'LU' => 'LUXEMBOURG',
                'LV' => 'LATVIA',
                'ME' => 'MONTENEGRO',
                'MK' => 'NORTH MACEDONIA',
                'MT' => 'MALTA',
                'NL' => 'NETHERLANDS',
                'NO' => 'NORWAY',
                'PL' => 'POLAND',
                'PT' => 'PORTUGAL',
                'RO' => 'ROMANIA',
                'RS' => 'SERBIA',
                'SE' => 'SWEDEN',
                'SI' => 'SLOVENIA',
                'SK' => 'SLOVAKIA',
                'TR' => 'TURKEY',
            );

            $country_code = $this->normalize_country_code( $country_code );
            if ( isset( $map[ $country_code ] ) ) {
                return $map[ $country_code ];
            }
            return strtoupper( sanitize_text_field( (string) $country_name ) );
        }

        /**
         * Format one NIH currency value.
         *
         * @param float $value Value.
         * @return string
         */
        private function format_nih_currency( $value ) {
            if ( abs( $value ) >= 1000000000 ) {
                return '$' . number_format_i18n( $value / 1000000000, 2 ) . 'B';
            }
            if ( abs( $value ) >= 1000000 ) {
                return '$' . number_format_i18n( $value / 1000000, 2 ) . 'M';
            }
            if ( abs( $value ) >= 1000 ) {
                return '$' . number_format_i18n( $value / 1000, 1 ) . 'K';
            }
            return '$' . number_format_i18n( $value, 0 );
        }

        /**
         * Fetch medical evidence payload.
         *
         * @param string $query Search query.
         * @param string $language Requested PubMed article language filter.
         * @return array<string,mixed>
         */
        private function fetch_medical_evidence_payload( $query, $language = 'any' ) {
            $language = 'any';
            $lang_map = $this->get_medical_language_options();
            $pubmed   = $this->search_pubmed_articles( $query, 8, 'any' );
            $trials   = $this->search_clinical_trials( $query, 8 );

            $overview_cards = array(
                array(
                    'label' => __( 'Search query', 'noesia-smart-waiter' ),
                    'value' => $query,
                    'meta'  => __( 'Internal evidence search', 'noesia-smart-waiter' ),
                ),
                array(
                    'label' => __( 'PubMed results', 'noesia-smart-waiter' ),
                    'value' => isset( $pubmed['total_formatted'] ) ? (string) $pubmed['total_formatted'] : number_format_i18n( isset( $pubmed['total'] ) ? (int) $pubmed['total'] : 0 ),
                    'meta'  => __( 'Official NCBI literature search', 'noesia-smart-waiter' ),
                ),
                array(
                    'label' => __( 'Clinical trial records', 'noesia-smart-waiter' ),
                    'value' => isset( $trials['total_formatted'] ) ? (string) $trials['total_formatted'] : number_format_i18n( isset( $trials['total'] ) ? (int) $trials['total'] : 0 ),
                    'meta'  => __( 'Official ClinicalTrials.gov registry search', 'noesia-smart-waiter' ),
                ),
            );

            return array(
                'status'         => 'ok',
                'source'         => 'PubMed + ClinicalTrials.gov',
                'query'          => $query,
                'language'       => $language,
                'language_label' => isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : (string) $lang_map['any'],
                'overview_cards' => $overview_cards,
                'pubmed'         => $pubmed,
                'trials'         => $trials,
                'note'           => __( 'Loco translates the interface only. Medical Evidence keeps external record content in the stable default official returned language flow.', 'noesia-smart-waiter' ),
            );
        }

        /**
         * Search PubMed articles.
         *
         * @param string $query Search query.
         * @param int    $limit Result limit.
         * @param string $language Requested PubMed article language filter.
         * @return array<string,mixed>
         */
        private function search_pubmed_articles( $query, $limit = 8, $language = 'any' ) {
            $limit    = max( 1, min( 10, absint( $limit ) ) );
            $language = 'any';
            $lang_map = $this->get_medical_language_options();
            $term     = $query;

            if ( 'any' !== $language ) {
                $term .= ' AND ' . $language . '[la]';
            }

            $search_url = add_query_arg(
                array(
                    'db'      => 'pubmed',
                    'retmode' => 'json',
                    'retmax'  => $limit,
                    'sort'    => 'relevance',
                    'term'    => $term,
                    'tool'    => 'noesia_swa_global_data_hub',
                ),
                'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi'
            );

            $response = wp_remote_get(
                $search_url,
                array(
                    'timeout' => 20,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => $this->build_remote_error_message( 'eutils.ncbi.nlm.nih.gov', $response ),
                );
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            if ( $code < 200 || $code >= 300 ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => __( 'PubMed returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }

            $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( ! is_array( $decoded ) || empty( $decoded['esearchresult'] ) || ! is_array( $decoded['esearchresult'] ) ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => __( 'PubMed returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            $ids   = isset( $decoded['esearchresult']['idlist'] ) && is_array( $decoded['esearchresult']['idlist'] ) ? array_values( array_filter( array_map( 'sanitize_text_field', $decoded['esearchresult']['idlist'] ) ) ) : array();
            $total = isset( $decoded['esearchresult']['count'] ) ? absint( $decoded['esearchresult']['count'] ) : count( $ids );

            if ( empty( $ids ) ) {
                return array(
                    'items'           => array(),
                    'total'           => 0,
                    'total_formatted' => '0',
                    /* translators: %s: selected language label. */
                    'notice'          => 'any' === $language ? __( 'No official PubMed records matched this query.', 'noesia-smart-waiter' ) : sprintf( __( 'No official PubMed records matched this query for %s.', 'noesia-smart-waiter' ), isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : $language ),
                );
            }

            $summary_map  = $this->fetch_pubmed_summaries( $ids );
            $abstract_map = $this->fetch_pubmed_abstracts( $ids );
            $items        = array();

            foreach ( $ids as $pmid ) {
                if ( ! isset( $summary_map[ $pmid ] ) || ! is_array( $summary_map[ $pmid ] ) ) {
                    continue;
                }

                $summary = $summary_map[ $pmid ];
                $pubdate = isset( $summary['pubdate'] ) ? sanitize_text_field( (string) $summary['pubdate'] ) : '';
                $journal = isset( $summary['fulljournalname'] ) ? sanitize_text_field( (string) $summary['fulljournalname'] ) : ( isset( $summary['source'] ) ? sanitize_text_field( (string) $summary['source'] ) : '' );
                $types   = array();
                if ( isset( $summary['pubtype'] ) && is_array( $summary['pubtype'] ) ) {
                    foreach ( $summary['pubtype'] as $pubtype ) {
                        $pubtype = sanitize_text_field( (string) $pubtype );
                        if ( '' !== $pubtype ) {
                            $types[] = $pubtype;
                        }
                    }
                }
                $authors = array();
                if ( isset( $summary['authors'] ) && is_array( $summary['authors'] ) ) {
                    foreach ( array_slice( $summary['authors'], 0, 3 ) as $author_row ) {
                        if ( is_array( $author_row ) && ! empty( $author_row['name'] ) ) {
                            $authors[] = sanitize_text_field( (string) $author_row['name'] );
                        }
                    }
                }

                $items[] = array(
                    'pmid'             => $pmid,
                    'title'            => isset( $summary['title'] ) ? sanitize_text_field( (string) $summary['title'] ) : __( 'Untitled PubMed record', 'noesia-smart-waiter' ),
                    'journal'          => $journal,
                    'pubdate'          => $pubdate,
                    'publication_type' => implode( ', ', array_slice( $types, 0, 3 ) ),
                    'authors'          => implode( ', ', $authors ),
                    'snippet'          => isset( $abstract_map[ $pmid ] ) && '' !== $abstract_map[ $pmid ] ? $abstract_map[ $pmid ] : __( 'Abstract snippet not available in the returned PubMed record.', 'noesia-smart-waiter' ),
                    'source_url'       => 'https://pubmed.ncbi.nlm.nih.gov/' . rawurlencode( $pmid ) . '/',
                    'language_filter'  => isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : (string) $lang_map['any'],
                );
            }

            return array(
                'items'           => $items,
                'total'           => $total,
                'total_formatted' => number_format_i18n( $total ),
                /* translators: %s: selected language label. */
                'notice'          => 'any' === $language ? __( 'PubMed results are shown inside the app. Open official source opens the PubMed record in a new tab.', 'noesia-smart-waiter' ) : sprintf( __( 'PubMed results are filtered to %s articles when possible. Open official source opens the PubMed record in a new tab.', 'noesia-smart-waiter' ), isset( $lang_map[ $language ] ) ? (string) $lang_map[ $language ] : $language ),
            );
        }

        /**
         * Fetch PubMed summaries.
         *
         * @param array<int,string> $ids PubMed IDs.
         * @return array<string,array<string,mixed>>
         */
        private function fetch_pubmed_summaries( $ids ) {
            if ( empty( $ids ) ) {
                return array();
            }

            $summary_url = add_query_arg(
                array(
                    'db'      => 'pubmed',
                    'retmode' => 'json',
                    'id'      => implode( ',', $ids ),
                    'tool'    => 'noesia_swa_global_data_hub',
                ),
                'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi'
            );
            $response = wp_remote_get(
                $summary_url,
                array(
                    'timeout' => 20,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );
            if ( is_wp_error( $response ) ) {
                return array();
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            if ( $code < 200 || $code >= 300 ) {
                return array();
            }
            $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( ! is_array( $decoded ) || empty( $decoded['result'] ) || ! is_array( $decoded['result'] ) ) {
                return array();
            }

            $map = array();
            foreach ( $ids as $pmid ) {
                if ( isset( $decoded['result'][ $pmid ] ) && is_array( $decoded['result'][ $pmid ] ) ) {
                    $map[ $pmid ] = $decoded['result'][ $pmid ];
                }
            }
            return $map;
        }

        /**
         * Fetch PubMed abstracts.
         *
         * @param array<int,string> $ids PubMed IDs.
         * @return array<string,string>
         */
        private function fetch_pubmed_abstracts( $ids ) {
            if ( empty( $ids ) ) {
                return array();
            }

            $fetch_url = add_query_arg(
                array(
                    'db'      => 'pubmed',
                    'retmode' => 'xml',
                    'id'      => implode( ',', $ids ),
                    'tool'    => 'noesia_swa_global_data_hub',
                ),
                'https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi'
            );
            $response = wp_remote_get(
                $fetch_url,
                array(
                    'timeout' => 20,
                )
            );
            if ( is_wp_error( $response ) ) {
                return array();
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            if ( $code < 200 || $code >= 300 ) {
                return array();
            }
            $body = wp_remote_retrieve_body( $response );
            if ( '' === trim( (string) $body ) || ! function_exists( 'simplexml_load_string' ) ) {
                return array();
            }

            libxml_use_internal_errors( true );
            $xml = simplexml_load_string( $body );
            if ( false === $xml ) {
                return array();
            }

            $map = array();
            if ( isset( $xml->PubmedArticle ) ) {
                foreach ( $xml->PubmedArticle as $article ) {
                    $pmid = isset( $article->MedlineCitation->PMID ) ? sanitize_text_field( (string) $article->MedlineCitation->PMID ) : '';
                    if ( '' === $pmid ) {
                        continue;
                    }
                    $parts = array();
                    if ( isset( $article->MedlineCitation->Article->Abstract->AbstractText ) ) {
                        foreach ( $article->MedlineCitation->Article->Abstract->AbstractText as $node ) {
                            $label = isset( $node['Label'] ) ? sanitize_text_field( (string) $node['Label'] ) : '';
                            $value = trim( wp_strip_all_tags( (string) $node ) );
                            if ( '' === $value ) {
                                continue;
                            }
                            $parts[] = '' !== $label ? $label . ': ' . $value : $value;
                        }
                    }
                    $snippet = trim( implode( ' ', $parts ) );
                    if ( '' !== $snippet ) {
                        if ( strlen( $snippet ) > 480 ) {
                            $snippet = substr( $snippet, 0, 477 ) . '...';
                        }
                        $map[ $pmid ] = $snippet;
                    }
                }
            }
            libxml_clear_errors();
            return $map;
        }

        /**
         * Search ClinicalTrials.gov studies.
         *
         * @param string $query Search query.
         * @param int    $limit Result limit.
         * @return array<string,mixed>
         */
        private function search_clinical_trials( $query, $limit = 8 ) {
            $limit = max( 1, min( 10, absint( $limit ) ) );
            $trial_url = add_query_arg(
                array(
                    'format'     => 'json',
                    'query.term' => $query,
                    'pageSize'   => $limit,
                    'countTotal' => 'true',
                ),
                'https://clinicaltrials.gov/api/query/studies'
            );
            $response = wp_remote_get(
                $trial_url,
                array(
                    'timeout' => 20,
                    'headers' => array(
                        'Accept' => 'application/json',
                    ),
                )
            );
            if ( is_wp_error( $response ) ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => $this->build_remote_error_message( 'clinicaltrials.gov', $response ),
                );
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            if ( $code < 200 || $code >= 300 ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => __( 'ClinicalTrials.gov returned an HTTP error.', 'noesia-smart-waiter' ),
                );
            }
            $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
            if ( ! is_array( $decoded ) ) {
                return array(
                    'items'  => array(),
                    'total'  => 0,
                    'notice' => __( 'ClinicalTrials.gov returned unreadable JSON data.', 'noesia-smart-waiter' ),
                );
            }

            $studies = isset( $decoded['studies'] ) && is_array( $decoded['studies'] ) ? $decoded['studies'] : array();
            $total   = isset( $decoded['totalCount'] ) ? absint( $decoded['totalCount'] ) : count( $studies );
            if ( empty( $studies ) ) {
                return array(
                    'items'           => array(),
                    'total'           => 0,
                    'total_formatted' => '0',
                    'notice'          => __( 'No official ClinicalTrials.gov records matched this query.', 'noesia-smart-waiter' ),
                );
            }

            $items = array();
            foreach ( $studies as $study ) {
                if ( ! is_array( $study ) ) {
                    continue;
                }
                $protocol       = isset( $study['protocolSection'] ) && is_array( $study['protocolSection'] ) ? $study['protocolSection'] : array();
                $id_module      = isset( $protocol['identificationModule'] ) && is_array( $protocol['identificationModule'] ) ? $protocol['identificationModule'] : array();
                $status_module  = isset( $protocol['statusModule'] ) && is_array( $protocol['statusModule'] ) ? $protocol['statusModule'] : array();
                $desc_module    = isset( $protocol['descriptionModule'] ) && is_array( $protocol['descriptionModule'] ) ? $protocol['descriptionModule'] : array();
                $cond_module    = isset( $protocol['conditionsModule'] ) && is_array( $protocol['conditionsModule'] ) ? $protocol['conditionsModule'] : array();
                $design_module  = isset( $protocol['designModule'] ) && is_array( $protocol['designModule'] ) ? $protocol['designModule'] : array();
                $sponsor_module = isset( $protocol['sponsorCollaboratorsModule'] ) && is_array( $protocol['sponsorCollaboratorsModule'] ) ? $protocol['sponsorCollaboratorsModule'] : array();
                $arms_module    = isset( $protocol['armsInterventionsModule'] ) && is_array( $protocol['armsInterventionsModule'] ) ? $protocol['armsInterventionsModule'] : array();

                $nct_id = isset( $id_module['nctId'] ) ? sanitize_text_field( (string) $id_module['nctId'] ) : '';
                $title  = isset( $id_module['briefTitle'] ) ? sanitize_text_field( (string) $id_module['briefTitle'] ) : __( 'Untitled clinical study', 'noesia-smart-waiter' );
                $status = isset( $status_module['overallStatus'] ) ? sanitize_text_field( (string) $status_module['overallStatus'] ) : '';
                $phase  = '';
                if ( isset( $design_module['phases'] ) && is_array( $design_module['phases'] ) ) {
                    $phase = implode( ', ', array_map( 'sanitize_text_field', $design_module['phases'] ) );
                }
                $conditions = '';
                if ( isset( $cond_module['conditions'] ) && is_array( $cond_module['conditions'] ) ) {
                    $conditions = implode( ', ', array_slice( array_map( 'sanitize_text_field', $cond_module['conditions'] ), 0, 3 ) );
                }
                $interventions = array();
                if ( isset( $arms_module['interventions'] ) && is_array( $arms_module['interventions'] ) ) {
                    foreach ( array_slice( $arms_module['interventions'], 0, 3 ) as $intervention ) {
                        if ( is_array( $intervention ) && ! empty( $intervention['name'] ) ) {
                            $interventions[] = sanitize_text_field( (string) $intervention['name'] );
                        }
                    }
                }
                $sponsor = isset( $sponsor_module['leadSponsor']['name'] ) ? sanitize_text_field( (string) $sponsor_module['leadSponsor']['name'] ) : '';
                $summary = isset( $desc_module['briefSummary'] ) ? trim( wp_strip_all_tags( (string) $desc_module['briefSummary'] ) ) : '';
                if ( strlen( $summary ) > 360 ) {
                    $summary = substr( $summary, 0, 357 ) . '...';
                }

                $items[] = array(
                    'nct_id'        => $nct_id,
                    'title'         => $title,
                    'status'        => $status,
                    'phase'         => $phase,
                    'conditions'    => $conditions,
                    'interventions' => implode( ', ', $interventions ),
                    'sponsor'       => $sponsor,
                    'summary'       => '' !== $summary ? $summary : __( 'Brief study summary not available in the returned registry record.', 'noesia-smart-waiter' ),
                    'source_url'    => '' !== $nct_id ? 'https://clinicaltrials.gov/study/' . rawurlencode( $nct_id ) : 'https://clinicaltrials.gov/',
                );
            }

            return array(
                'items'           => $items,
                'total'           => $total,
                'total_formatted' => number_format_i18n( $total ),
                'notice'          => __( 'ClinicalTrials.gov results are registry records, not conclusions about treatment effectiveness. Open official source opens the study record in a new tab.', 'noesia-smart-waiter' ),
            );
        }


        /**
         * Clear cache for one country.
         *
         * @param string $country_code Country code.
         * @return void
         */
        private function clear_country_cache( $country_code ) {
            $country_code = strtolower( sanitize_key( $country_code ) );
            if ( '' === $country_code ) {
                return;
            }
            foreach ( array( 'arrivals', 'nights', 'occupancy', 'development', 'digital', 'population', 'imf_macro', 'oecd', 'health', 'medical' ) as $suffix ) {
                delete_transient( self::CACHE_PREFIX . $suffix . '_' . $country_code );
            }
        }

        /**
         * Normalize country code.
         *
         * @param string $value Raw value.
         * @return string
         */
        private function normalize_country_code( $value ) {
            return strtoupper( preg_replace( '/[^A-Z]/', '', strtoupper( sanitize_text_field( $value ) ) ) );
        }

        /**
         * Build a network error message.
         *
         * @param string          $service_label Service label.
         * @param WP_Error|string $error Error.
         * @return string
         */
        private function build_remote_error_message( $service_label, $error ) {
            $message = __( 'The official data provider did not answer successfully.', 'noesia-smart-waiter' );
            if ( is_wp_error( $error ) ) {
                $raw = sanitize_text_field( $error->get_error_message() );
                if ( '' !== $raw ) {
                    $message = $raw;
                }
            }
            /* translators: 1: remote transport error message, 2: remote service hostname. */
            return sprintf( __( '%1$s Please verify outbound HTTPS, cURL/SSL support, firewall rules, and hosting restrictions for %2$s.', 'noesia-smart-waiter' ), $message, $service_label );
        }
    }
}
