<?php
/**
 * Weather service for Noesia Smart Waiter.
 *
 * Extracted from the legacy dashboard data helper so Weather logic can live
 * inside the Weather module instead of the central kernel or dashboard module.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_SWA_Weather_Service' ) ) {
    /**
     * Weather service.
     */
    class Noesia_SWA_Weather_Service {

public function normalize_country_code( $raw ) {
			if ( ! is_string( $raw ) ) {
				return '';
			}

			$raw = trim( $raw );
			if ( '' === $raw ) {
				return '';
			}

			if ( preg_match( '/\b([A-Z]{2})\b/i', $raw, $matches ) ) {
				return strtoupper( sanitize_key( $matches[1] ) );
			}

			$key = strtoupper( sanitize_key( $raw ) );

			return ( strlen( $key ) >= 2 ) ? substr( $key, 0, 2 ) : '';
		}

		/**
		 * Resolve the dashboard weather language from settings.
		 *
		 * @param array<string,mixed> $options Plugin options.
		 * @return string
		 */
		public function get_weather_lang( $options ) {
			$options       = is_array( $options ) ? $options : array();
			$bridge_lang   = isset( $options['news_language'] ) ? sanitize_key( (string) $options['news_language'] ) : '';
			$weather_lang  = isset( $options['weather_language'] ) ? sanitize_key( (string) $options['weather_language'] ) : '';
			$allowed_langs = array( 'en', 'el', 'fr', 'de', 'it', 'es' );

			if ( in_array( $bridge_lang, $allowed_langs, true ) ) {
				return $bridge_lang;
			}

			if ( in_array( $weather_lang, $allowed_langs, true ) ) {
				return $weather_lang;
			}

			return 'en';
		}

		/**
		 * Build a compact location snapshot with time zone and current temperature.
		 *
		 * @param string              $city              City name.
		 * @param string              $country           ISO alpha-2 country code.
		 * @param string              $timezone_override Optional timezone identifier.
		 * @param array<string,mixed> $options           Plugin options.
		 * @param bool                $allow_remote      Whether remote fetches are allowed.
		 * @return array<string,mixed>
		 */
		public function get_location_snapshot( $city, $country, $timezone_override, $options = array(), $allow_remote = true ) {
			$options           = is_array( $options ) ? $options : array();
			$city              = sanitize_text_field( (string) $city );
			$country           = $this->normalize_country_code( (string) $country );
			$timezone_override = sanitize_text_field( (string) $timezone_override );
			$allow_remote      = (bool) $allow_remote;
			$weather_lang      = $this->get_weather_lang( $options );
			$units_raw         = isset( $options['weather_units'] ) ? sanitize_key( (string) $options['weather_units'] ) : 'metric';
			$units             = in_array( $units_raw, array( 'metric', 'imperial' ), true ) ? $units_raw : 'metric';
			$units_label       = ( 'imperial' === $units ) ? '°F' : '°C';
			$temp_unit         = ( 'imperial' === $units ) ? 'fahrenheit' : 'celsius';

			$snapshot = array(
				'city'       => $city,
				'country'    => $country,
				'country_name' => '',
				'timezone'   => '',
				'time_label' => '—',
				'day_label'  => '',
				'temp_label' => '—',
				'condition_label' => '',
				'rain_label' => '—',
				'wind_label' => '—',
				'sunrise_label' => '—',
				'sunset_label' => '—',
				'daylight_label' => '—',
				'daylight_state' => __( 'Day', 'noesia-smart-waiter' ),
				'lat'        => null,
				'lng'        => null,
			);

			if ( '' === $city ) {
				return $snapshot;
			}

			$cache_key = 'noesia_swa_weather_loc_' . md5( strtolower( $city ) . '|' . strtolower( $country ) . '|' . strtolower( $timezone_override ) . '|' . $units . '|' . $weather_lang );
			$cached    = get_transient( $cache_key );

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

			if ( ! $allow_remote ) {
				if ( '' !== $timezone_override ) {
					try {
						$date = new DateTimeImmutable( 'now', new DateTimeZone( $timezone_override ) );
						$snapshot['timezone']   = $timezone_override;
						$snapshot['time_label'] = $date->format( 'H:i' );
						$snapshot['day_label']  = wp_date( 'D', $date->getTimestamp(), new DateTimeZone( $timezone_override ) );
					} catch ( Exception $exception ) {
						$snapshot['timezone'] = '';
					}
				}

				return $snapshot;
			}

			$geo_args = array(
				'name'     => $city,
				'count'    => 8,
				'language' => $weather_lang,
			);

			if ( '' !== $country ) {
				$geo_args['country'] = $country;
			}

			$geo_url = add_query_arg( $geo_args, 'https://geocoding-api.open-meteo.com/v1/search' );
			$geo_res = wp_safe_remote_get(
				$geo_url,
				array(
					'timeout'   => 10,
					'sslverify' => true,
					'headers'   => array( 'Accept' => 'application/json' ),
				)
			);

			if ( is_wp_error( $geo_res ) ) {
				return $snapshot;
			}

			$geo_body = json_decode( wp_remote_retrieve_body( $geo_res ), true );

			if ( ! is_array( $geo_body ) || empty( $geo_body['results'] ) || ! is_array( $geo_body['results'] ) ) {
				return $snapshot;
			}

			$match = $geo_body['results'][0];

			if ( '' !== $country ) {
				foreach ( $geo_body['results'] as $row ) {
					if ( ! is_array( $row ) ) {
						continue;
					}

					$row_cc = isset( $row['country_code'] ) ? strtoupper( sanitize_key( (string) $row['country_code'] ) ) : '';

					if ( $row_cc === $country ) {
						$match = $row;
						break;
					}
				}
			}

			$latitude  = isset( $match['latitude'] ) && is_numeric( $match['latitude'] ) ? (float) $match['latitude'] : null;
			$longitude = isset( $match['longitude'] ) && is_numeric( $match['longitude'] ) ? (float) $match['longitude'] : null;

			if ( null === $latitude || null === $longitude ) {
				return $snapshot;
			}

			$place_name = isset( $match['name'] ) ? sanitize_text_field( (string) $match['name'] ) : $city;
			$place_cc   = isset( $match['country_code'] ) ? strtoupper( sanitize_key( (string) $match['country_code'] ) ) : $country;
			$country_name = isset( $match['country'] ) ? sanitize_text_field( (string) $match['country'] ) : '';
			$timezone   = isset( $match['timezone'] ) ? sanitize_text_field( (string) $match['timezone'] ) : '';

			if ( '' !== $timezone_override ) {
				try {
					new DateTimeZone( $timezone_override );
					$timezone = $timezone_override;
				} catch ( Exception $exception ) {
					$timezone = $timezone;
				}
			}

			$snapshot['city']     = $place_name;
			$snapshot['country']  = $place_cc;
			$snapshot['country_name'] = $country_name;
			$snapshot['timezone'] = $timezone;
			$snapshot['lat']      = $latitude;
			$snapshot['lng']      = $longitude;

			if ( '' !== $timezone ) {
				try {
					$date = new DateTimeImmutable( 'now', new DateTimeZone( $timezone ) );
					$snapshot['time_label'] = $date->format( 'H:i' );
					$snapshot['day_label']  = wp_date( 'D', $date->getTimestamp(), new DateTimeZone( $timezone ) );
				} catch ( Exception $exception ) {
					$snapshot['time_label'] = '—';
					$snapshot['day_label']  = '';
				}
			}

			$forecast_url = add_query_arg(
				array(
					'latitude'         => $latitude,
					'longitude'        => $longitude,
					'current_weather'  => 'true',
					'daily'            => 'weathercode,precipitation_probability_max,windspeed_10m_max,sunrise,sunset',
					'timezone'         => 'auto',
					'temperature_unit' => $temp_unit,
				),
				'https://api.open-meteo.com/v1/forecast'
			);

			$forecast_res = wp_safe_remote_get(
				$forecast_url,
				array(
					'timeout'   => 10,
					'sslverify' => true,
					'headers'   => array( 'Accept' => 'application/json' ),
				)
			);

			if ( ! is_wp_error( $forecast_res ) ) {
				$forecast_body = json_decode( wp_remote_retrieve_body( $forecast_res ), true );

				if ( is_array( $forecast_body ) && ! empty( $forecast_body['current_weather'] ) && is_array( $forecast_body['current_weather'] ) ) {
					$current_temp = isset( $forecast_body['current_weather']['temperature'] ) ? round( (float) $forecast_body['current_weather']['temperature'] ) : null;
					$current_code = isset( $forecast_body['current_weather']['weathercode'] ) ? absint( $forecast_body['current_weather']['weathercode'] ) : 0;
					$current_wind = isset( $forecast_body['current_weather']['windspeed'] ) ? round( (float) $forecast_body['current_weather']['windspeed'] ) : null;
					$is_day = isset( $forecast_body['current_weather']['is_day'] ) ? absint( $forecast_body['current_weather']['is_day'] ) : 1;

					if ( null !== $current_temp ) {
						$snapshot['temp_label'] = (string) $current_temp . $units_label;
					}

					if ( $current_code > 0 ) {
						$snapshot['condition_label'] = $this->get_weather_code_short_for_lang( $current_code, $weather_lang );
					}

					if ( null !== $current_wind ) {
						$snapshot['wind_label'] = (string) $current_wind . ' km/h';
					}

					$snapshot['daylight_state'] = ( 1 === $is_day ) ? __( 'Day', 'noesia-smart-waiter' ) : __( 'Night', 'noesia-smart-waiter' );
				}

				if ( is_array( $forecast_body ) && ! empty( $forecast_body['daily'] ) && is_array( $forecast_body['daily'] ) ) {
					$daily = $forecast_body['daily'];
					$precipitation = isset( $daily['precipitation_probability_max'] ) && is_array( $daily['precipitation_probability_max'] ) ? $daily['precipitation_probability_max'] : array();
					$wind_max = isset( $daily['windspeed_10m_max'] ) && is_array( $daily['windspeed_10m_max'] ) ? $daily['windspeed_10m_max'] : array();
					$sunrise = isset( $daily['sunrise'] ) && is_array( $daily['sunrise'] ) ? $daily['sunrise'] : array();
					$sunset = isset( $daily['sunset'] ) && is_array( $daily['sunset'] ) ? $daily['sunset'] : array();

					if ( isset( $precipitation[0] ) ) {
						$snapshot['rain_label'] = absint( $precipitation[0] ) . '%';
					}

					if ( isset( $wind_max[0] ) && '—' === $snapshot['wind_label'] ) {
						$snapshot['wind_label'] = round( (float) $wind_max[0] ) . ' km/h';
					}

					if ( isset( $sunrise[0] ) ) {
						$snapshot['sunrise_label'] = $this->format_weather_time_label( (string) $sunrise[0] );
					}

					if ( isset( $sunset[0] ) ) {
						$snapshot['sunset_label'] = $this->format_weather_time_label( (string) $sunset[0] );
					}

					if ( isset( $sunrise[0], $sunset[0] ) ) {
						$sunrise_ts = strtotime( (string) $sunrise[0] );
						$sunset_ts = strtotime( (string) $sunset[0] );

						if ( false !== $sunrise_ts && false !== $sunset_ts && $sunset_ts > $sunrise_ts ) {
							$snapshot['daylight_label'] = $this->format_weather_duration_label( (int) $sunset_ts - (int) $sunrise_ts );
						}
					}
				}
			}

			set_transient( $cache_key, $snapshot, 30 * MINUTE_IN_SECONDS );

			return $snapshot;
		}

		/**
		 * Format a compact duration label for weather compare cards.
		 *
		 * @param int $seconds Duration in seconds.
		 * @return string
		 */
		private function format_weather_duration_label( $seconds ) {
			$seconds = absint( (int) $seconds );

			if ( $seconds <= 0 ) {
				return '—';
			}

			$hours   = (int) floor( $seconds / HOUR_IN_SECONDS );
			$minutes = (int) floor( ( $seconds % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS );

			return sprintf(
				/* translators: 1: hours, 2: minutes */
				__( '%1$sh %2$sm', 'noesia-smart-waiter' ),
				number_format_i18n( $hours ),
				number_format_i18n( $minutes )
			);
		}

		/**
		 * Translate small dashboard weather labels.
		 *
		 * @param string $key  Label key.
		 * @param string $lang Language code.
		 * @return string
		 */
		public function get_weather_ui_label( $key, $lang ) {
			$lang = in_array( $lang, array( 'en', 'el', 'fr', 'de', 'it', 'es' ), true ) ? $lang : 'en';
			$map  = array(
				'today' => array(
					'en' => 'Today',
					'el' => 'Σήμερα',
					'fr' => "Aujourd'hui",
					'de' => 'Heute',
					'it' => 'Oggi',
					'es' => 'Hoy',
				),
				'rain' => array(
					'en' => 'Rain',
					'el' => 'Βροχή',
					'fr' => 'Pluie',
					'de' => 'Regen',
					'it' => 'Pioggia',
					'es' => 'Lluvia',
				),
'wind' => array(
	'en' => 'Wind',
	'el' => 'Άνεμος',
	'fr' => 'Vent',
	'de' => 'Wind',
	'it' => 'Vento',
	'es' => 'Viento',
),
'gust' => array(
	'en' => 'Gusts',
	'el' => 'Ριπές',
	'fr' => 'Rafales',
	'de' => 'Böen',
	'it' => 'Raffiche',
	'es' => 'Ráfagas',
),
				'min' => array(
					'en' => 'Min',
					'el' => 'Ελάχ.',
					'fr' => 'Min',
					'de' => 'Min',
					'it' => 'Min',
					'es' => 'Mín.',
				),
				'max' => array(
					'en' => 'Max',
					'el' => 'Μέγ.',
					'fr' => 'Max',
					'de' => 'Max',
					'it' => 'Max',
					'es' => 'Máx.',
				),
			);

			if ( isset( $map[ $key ][ $lang ] ) ) {
				return $map[ $key ][ $lang ];
			}

			return isset( $map[ $key ]['en'] ) ? $map[ $key ]['en'] : $key;
		}

		/**
		 * Translate Open-Meteo codes using the dashboard-selected language.
		 *
		 * @param int    $code Weather code.
		 * @param string $lang Language code.
		 * @return string
		 */
		public function get_weather_code_short_for_lang( $code, $lang ) {
			$code = absint( $code );
			$lang = in_array( $lang, array( 'en', 'el', 'fr', 'de', 'it', 'es' ), true ) ? $lang : 'en';
			$map  = array(
				'clear' => array(
					'en' => 'Clear',
					'el' => 'Αίθριος',
					'fr' => 'Dégagé',
					'de' => 'Klar',
					'it' => 'Sereno',
					'es' => 'Despejado',
				),
				'mainly_clear' => array(
					'en' => 'Mainly clear',
					'el' => 'Κυρίως αίθριος',
					'fr' => 'Plutôt dégagé',
					'de' => 'Überwiegend klar',
					'it' => 'Prevalentemente sereno',
					'es' => 'Mayormente despejado',
				),
				'partly_cloudy' => array(
					'en' => 'Partly cloudy',
					'el' => 'Μερικώς συννεφιασμένος',
					'fr' => 'Partiellement nuageux',
					'de' => 'Teilweise bewölkt',
					'it' => 'Parzialmente nuvoloso',
					'es' => 'Parcialmente nublado',
				),
				'cloudy' => array(
					'en' => 'Cloudy',
					'el' => 'Συννεφιασμένος',
					'fr' => 'Nuageux',
					'de' => 'Bewölkt',
					'it' => 'Nuvoloso',
					'es' => 'Nublado',
				),
				'fog' => array(
					'en' => 'Fog',
					'el' => 'Ομίχλη',
					'fr' => 'Brouillard',
					'de' => 'Nebel',
					'it' => 'Nebbia',
					'es' => 'Niebla',
				),
				'drizzle' => array(
					'en' => 'Drizzle',
					'el' => 'Ψιχάλες',
					'fr' => 'Bruine',
					'de' => 'Nieselregen',
					'it' => 'Pioviggine',
					'es' => 'Llovizna',
				),
				'rain' => array(
					'en' => 'Rain',
					'el' => 'Βροχή',
					'fr' => 'Pluie',
					'de' => 'Regen',
					'it' => 'Pioggia',
					'es' => 'Lluvia',
				),
				'snow' => array(
					'en' => 'Snow',
					'el' => 'Χιόνι',
					'fr' => 'Neige',
					'de' => 'Schnee',
					'it' => 'Neve',
					'es' => 'Nieve',
				),
				'thunderstorm' => array(
					'en' => 'Thunderstorm',
					'el' => 'Καταιγίδα',
					'fr' => 'Orage',
					'de' => 'Gewitter',
					'it' => 'Temporale',
					'es' => 'Tormenta',
				),
				'weather' => array(
					'en' => 'Weather',
					'el' => 'Καιρός',
					'fr' => 'Météo',
					'de' => 'Wetter',
					'it' => 'Meteo',
					'es' => 'Tiempo',
				),
			);

			$key = 'weather';
			if ( 0 === $code ) {
				$key = 'clear';
			} elseif ( 1 === $code ) {
				$key = 'mainly_clear';
			} elseif ( 2 === $code ) {
				$key = 'partly_cloudy';
			} elseif ( 3 === $code ) {
				$key = 'cloudy';
			} elseif ( in_array( $code, array( 45, 48 ), true ) ) {
				$key = 'fog';
			} elseif ( in_array( $code, array( 51, 53, 55, 56, 57 ), true ) ) {
				$key = 'drizzle';
			} elseif ( in_array( $code, array( 61, 63, 65, 66, 67, 80, 81, 82 ), true ) ) {
				$key = 'rain';
			} elseif ( in_array( $code, array( 71, 73, 75, 77, 85, 86 ), true ) ) {
				$key = 'snow';
			} elseif ( in_array( $code, array( 95, 96, 99 ), true ) ) {
				$key = 'thunderstorm';
			}

			return isset( $map[ $key ][ $lang ] ) ? $map[ $key ][ $lang ] : $map[ $key ]['en'];
		}

		/**
		 * Format forecast weekday in the language chosen in settings.
		 *
		 * @param string $date YYYY-MM-DD date.
		 * @param string $lang Language code.
		 * @return string
		 */
		public function format_weather_day_name( $date, $lang ) {
			$timestamp = strtotime( (string) $date . ' 12:00:00' );
			$index     = false !== $timestamp ? (int) gmdate( 'w', $timestamp ) : 0;
			$map       = array(
				'en' => array( 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ),
				'el' => array( 'Κυρ', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ' ),
				'fr' => array( 'Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam' ),
				'de' => array( 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa' ),
				'it' => array( 'Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab' ),
				'es' => array( 'Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb' ),
			);
			$lang      = in_array( $lang, array_keys( $map ), true ) ? $lang : 'en';

			return isset( $map[ $lang ][ $index ] ) ? $map[ $lang ][ $index ] : '—';
		}

		/**
		 * Format short month/day label in settings language.
		 *
		 * @param string $date YYYY-MM-DD date.
		 * @param string $lang Language code.
		 * @return string
		 */
		public function format_weather_day_date( $date, $lang ) {
			$timestamp = strtotime( (string) $date . ' 12:00:00' );
			if ( false === $timestamp ) {
				return '—';
			}

			$day    = (string) gmdate( 'j', $timestamp );
			$monthi = (int) gmdate( 'n', $timestamp );
			$months = array(
				'en' => array( 1 => 'Jan', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'May', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug', 9 => 'Sep', 10 => 'Oct', 11 => 'Nov', 12 => 'Dec' ),
				'el' => array( 1 => 'Ιαν', 2 => 'Φεβ', 3 => 'Μαρ', 4 => 'Απρ', 5 => 'Μαΐ', 6 => 'Ιουν', 7 => 'Ιουλ', 8 => 'Αυγ', 9 => 'Σεπ', 10 => 'Οκτ', 11 => 'Νοε', 12 => 'Δεκ' ),
				'fr' => array( 1 => 'janv.', 2 => 'févr.', 3 => 'mars', 4 => 'avr.', 5 => 'mai', 6 => 'juin', 7 => 'juil.', 8 => 'août', 9 => 'sept.', 10 => 'oct.', 11 => 'nov.', 12 => 'déc.' ),
				'de' => array( 1 => 'Jan', 2 => 'Feb', 3 => 'Mär', 4 => 'Apr', 5 => 'Mai', 6 => 'Jun', 7 => 'Jul', 8 => 'Aug', 9 => 'Sep', 10 => 'Okt', 11 => 'Nov', 12 => 'Dez' ),
				'it' => array( 1 => 'gen', 2 => 'feb', 3 => 'mar', 4 => 'apr', 5 => 'mag', 6 => 'giu', 7 => 'lug', 8 => 'ago', 9 => 'set', 10 => 'ott', 11 => 'nov', 12 => 'dic' ),
				'es' => array( 1 => 'ene', 2 => 'feb', 3 => 'mar', 4 => 'abr', 5 => 'may', 6 => 'jun', 7 => 'jul', 8 => 'ago', 9 => 'sept', 10 => 'oct', 11 => 'nov', 12 => 'dic' ),
			);
			$lang   = isset( $months[ $lang ] ) ? $lang : 'en';
			$month  = isset( $months[ $lang ][ $monthi ] ) ? $months[ $lang ][ $monthi ] : $months['en'][ $monthi ];

			return $day . ' ' . $month;
		}

		/**
		 * Format a compact local clock label from an API datetime string.
		 *
		 * @param string $value Date/time string.
		 * @return string
		 */
		private function format_weather_time_label( $value ) {
			$value = sanitize_text_field( (string) $value );

			if ( '' === $value ) {
				return '—';
			}

			if ( preg_match( '/T(\d{2}:\d{2})/', $value, $matches ) ) {
				return (string) $matches[1];
			}

			$timestamp = strtotime( $value );

			if ( false === $timestamp ) {
				return '—';
			}

			return gmdate( 'H:i', $timestamp );
		}

		/**
		 * Build a compact AQI label for dashboard surfaces.
		 *
		 * @param int|null $aqi AQI value when available.
		 * @return string
		 */
		private function get_air_quality_label( $aqi ) {
			if ( null === $aqi ) {
				return 'AQI —';
			}

			return 'AQI ' . (string) absint( $aqi );
		}

		/**
		 * Fetch the current AQI from Open-Meteo when available.
		 *
		 * @param float $latitude Latitude.
		 * @param float $longitude Longitude.
		 * @return int|null
		 */
		private function get_current_air_quality_index( $latitude, $longitude ) {
			$aqi_url = add_query_arg(
				array(
					'latitude'  => (float) $latitude,
					'longitude' => (float) $longitude,
					'hourly'    => 'us_aqi',
					'timezone'  => 'auto',
				),
				'https://air-quality-api.open-meteo.com/v1/air-quality'
			);

			$aqi_res = wp_safe_remote_get(
				$aqi_url,
				array(
					'timeout'   => 10,
					'sslverify' => true,
					'headers'   => array( 'Accept' => 'application/json' ),
				)
			);

			if ( is_wp_error( $aqi_res ) ) {
				return null;
			}

			$aqi_body = json_decode( wp_remote_retrieve_body( $aqi_res ), true );

			if ( ! is_array( $aqi_body ) || empty( $aqi_body['hourly']['us_aqi'] ) || ! is_array( $aqi_body['hourly']['us_aqi'] ) ) {
				return null;
			}

			foreach ( $aqi_body['hourly']['us_aqi'] as $aqi_value ) {
				if ( null === $aqi_value || '' === $aqi_value ) {
					continue;
				}

				if ( is_numeric( $aqi_value ) ) {
					return absint( round( (float) $aqi_value ) );
				}
			}

			return null;
		}


		/**
		 * Convert Open-Meteo weather code to short text.
		 *
		 * @param int $code Weather code.
		 * @return string
		 */
		public function get_weather_code_short( $code ) {
			$code = absint( $code );

			if ( 0 === $code ) {
				return __( 'Clear', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 1, 2, 3 ), true ) ) {
				return __( 'Partly cloudy', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 45, 48 ), true ) ) {
				return __( 'Fog', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 51, 53, 55, 56, 57 ), true ) ) {
				return __( 'Drizzle', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 61, 63, 65, 66, 67, 80, 81, 82 ), true ) ) {
				return __( 'Rain', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 71, 73, 75, 77, 85, 86 ), true ) ) {
				return __( 'Snow', 'noesia-smart-waiter' );
			}
			if ( in_array( $code, array( 95, 96, 99 ), true ) ) {
				return __( 'Thunderstorm', 'noesia-smart-waiter' );
			}

			return __( 'Weather', 'noesia-smart-waiter' );
		}

		/**
		 * Return an emoji icon for a weather code.
		 *
		 * @param int $code Weather code.
		 * @return string
		 */
		public function get_weather_icon( $code ) {
			$code = absint( $code );

			if ( 0 === $code ) {
				return '☀️';
			}
			if ( in_array( $code, array( 1, 2, 3 ), true ) ) {
				return '⛅';
			}
			if ( in_array( $code, array( 45, 48 ), true ) ) {
				return '🌫️';
			}
			if ( in_array( $code, array( 51, 53, 55, 56, 57 ), true ) ) {
				return '🌦️';
			}
			if ( in_array( $code, array( 61, 63, 65, 66, 67, 80, 81, 82 ), true ) ) {
				return '🌧️';
			}
			if ( in_array( $code, array( 71, 73, 75, 77, 85, 86 ), true ) ) {
				return '🌨️';
			}
			if ( in_array( $code, array( 95, 96, 99 ), true ) ) {
				return '⛈️';
			}

			return '🌡️';
		}

		/**
		 * Translate notable-day phrases using the Dashboard Language setting.
		 *
		 * @param string $key  Phrase key.
		 * @param string $lang Language code.
		 * @return string
		 */
		private function get_notable_day_phrase( $key, $lang ) {
			$lang = in_array( $lang, array( 'en', 'el', 'fr', 'de', 'it', 'es' ), true ) ? $lang : 'en';
			$map  = array(
				'world_pizza_day' => array(
					'en' => 'World Pizza Day',
					'el' => 'Παγκόσμια Ημέρα Πίτσας',
					'fr' => 'Journée mondiale de la pizza',
					'de' => 'Welt-Pizza-Tag',
					'it' => 'Giornata mondiale della pizza',
					'es' => 'Día Mundial de la Pizza',
				),
				'valentines_day' => array(
					'en' => "Valentine's Day",
					'el' => 'Ημέρα του Αγίου Βαλεντίνου',
					'fr' => 'Saint-Valentin',
					'de' => 'Valentinstag',
					'it' => 'San Valentino',
					'es' => 'Día de San Valentín',
				),
				'international_womens_day' => array(
					'en' => "International Women's Day",
					'el' => 'Παγκόσμια Ημέρα της Γυναίκας',
					'fr' => 'Journée internationale des femmes',
					'de' => 'Internationaler Frauentag',
					'it' => 'Giornata internazionale della donna',
					'es' => 'Día Internacional de la Mujer',
				),
				'international_day_of_happiness' => array(
					'en' => 'International Day of Happiness',
					'el' => 'Διεθνής Ημέρα Ευτυχίας',
					'fr' => 'Journée internationale du bonheur',
					'de' => 'Internationaler Tag des Glücks',
					'it' => 'Giornata internazionale della felicità',
					'es' => 'Día Internacional de la Felicidad',
				),
				'world_health_day' => array(
					'en' => 'World Health Day',
					'el' => 'Παγκόσμια Ημέρα Υγείας',
					'fr' => 'Journée mondiale de la santé',
					'de' => 'Weltgesundheitstag',
					'it' => 'Giornata mondiale della salute',
					'es' => 'Día Mundial de la Salud',
				),
				'earth_day' => array(
					'en' => 'Earth Day',
					'el' => 'Ημέρα της Γης',
					'fr' => 'Jour de la Terre',
					'de' => 'Tag der Erde',
					'it' => 'Giornata della Terra',
					'es' => 'Día de la Tierra',
				),
				'world_environment_day' => array(
					'en' => 'World Environment Day',
					'el' => 'Παγκόσμια Ημέρα Περιβάλλοντος',
					'fr' => 'Journée mondiale de l’environnement',
					'de' => 'Weltumwelttag',
					'it' => 'Giornata mondiale dell’ambiente',
					'es' => 'Día Mundial del Medio Ambiente',
				),
				'international_day_of_yoga' => array(
					'en' => 'International Day of Yoga',
					'el' => 'Διεθνής Ημέρα Γιόγκα',
					'fr' => 'Journée internationale du yoga',
					'de' => 'Internationaler Tag des Yoga',
					'it' => 'Giornata internazionale dello yoga',
					'es' => 'Día Internacional del Yoga',
				),
				'world_chocolate_day' => array(
					'en' => 'World Chocolate Day',
					'el' => 'Παγκόσμια Ημέρα Σοκολάτας',
					'fr' => 'Journée mondiale du chocolat',
					'de' => 'Weltschokoladentag',
					'it' => 'Giornata mondiale del cioccolato',
					'es' => 'Día Mundial del Chocolate',
				),
				'international_beer_day' => array(
					'en' => 'International Beer Day',
					'el' => 'Διεθνής Ημέρα Μπίρας',
					'fr' => 'Journée internationale de la bière',
					'de' => 'Internationaler Tag des Bieres',
					'it' => 'Giornata internazionale della birra',
					'es' => 'Día Internacional de la Cerveza',
				),
				'international_day_of_peace' => array(
					'en' => 'International Day of Peace',
					'el' => 'Διεθνής Ημέρα Ειρήνης',
					'fr' => 'Journée internationale de la paix',
					'de' => 'Internationaler Tag des Friedens',
					'it' => 'Giornata internazionale della pace',
					'es' => 'Día Internacional de la Paz',
				),
				'world_tourism_day' => array(
					'en' => 'World Tourism Day',
					'el' => 'Παγκόσμια Ημέρα Τουρισμού',
					'fr' => 'Journée mondiale du tourisme',
					'de' => 'Welttourismustag',
					'it' => 'Giornata mondiale del turismo',
					'es' => 'Día Mundial del Turismo',
				),
				'international_coffee_day' => array(
					'en' => 'International Coffee Day',
					'el' => 'Διεθνής Ημέρα Καφέ',
					'fr' => 'Journée internationale du café',
					'de' => 'Internationaler Tag des Kaffees',
					'it' => 'Giornata internazionale del caffè',
					'es' => 'Día Internacional del Café',
				),
				'world_food_day' => array(
					'en' => 'World Food Day',
					'el' => 'Παγκόσμια Ημέρα Τροφίμων',
					'fr' => 'Journée mondiale de l’alimentation',
					'de' => 'Welternährungstag',
					'it' => 'Giornata mondiale dell’alimentazione',
					'es' => 'Día Mundial de la Alimentación',
				),
				'world_kindness_day' => array(
					'en' => 'World Kindness Day',
					'el' => 'Παγκόσμια Ημέρα Καλοσύνης',
					'fr' => 'Journée mondiale de la gentillesse',
					'de' => 'Welttag der Freundlichkeit',
					'it' => 'Giornata mondiale della gentilezza',
					'es' => 'Día Mundial de la Bondad',
				),
				'international_volunteer_day' => array(
					'en' => 'International Volunteer Day',
					'el' => 'Διεθνής Ημέρα Εθελοντισμού',
					'fr' => 'Journée internationale des volontaires',
					'de' => 'Internationaler Tag des Ehrenamts',
					'it' => 'Giornata internazionale del volontariato',
					'es' => 'Día Internacional del Voluntariado',
				),
				'no_notable_date_available' => array(
					'en' => 'No notable date available.',
					'el' => 'Δεν υπάρχει διαθέσιμη σημαντική ημερομηνία.',
					'fr' => 'Aucune date notable disponible.',
					'de' => 'Kein wichtiges Datum verfügbar.',
					'it' => 'Nessuna data importante disponibile.',
					'es' => 'No hay fecha destacada disponible.',
				),
			);

			return isset( $map[ $key ][ $lang ] ) ? $map[ $key ][ $lang ] : ( isset( $map[ $key ]['en'] ) ? $map[ $key ]['en'] : (string) $key );
		}


		/**
		 * Return the next curated global observance for the dashboard card.
		 *
		 * @return array<string, string>
		 */
		public function get_next_notable_day( $lang = 'en' ) {
			$current_timestamp = (int) current_time( 'timestamp' );
			$current_year      = (int) wp_date( 'Y', $current_timestamp );
			$items             = array();
			$lang              = in_array( $lang, array( 'en', 'el', 'fr', 'de', 'it', 'es' ), true ) ? $lang : 'en';
			$definitions       = array(
				array(
					'kind'   => 'fixed',
					'date'   => '02-09',
					'phrase' => 'world_pizza_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '02-14',
					'phrase' => 'valentines_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '03-08',
					'phrase' => 'international_womens_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '03-20',
					'phrase' => 'international_day_of_happiness',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '04-07',
					'phrase' => 'world_health_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '04-22',
					'phrase' => 'earth_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '06-05',
					'phrase' => 'world_environment_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '06-21',
					'phrase' => 'international_day_of_yoga',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '07-07',
					'phrase' => 'world_chocolate_day',
				),
				array(
					'kind'    => 'dynamic',
					'pattern' => 'first friday of august',
					'phrase'  => 'international_beer_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '09-21',
					'phrase' => 'international_day_of_peace',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '09-27',
					'phrase' => 'world_tourism_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '10-01',
					'phrase' => 'international_coffee_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '10-16',
					'phrase' => 'world_food_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '11-13',
					'phrase' => 'world_kindness_day',
				),
				array(
					'kind'   => 'fixed',
					'date'   => '12-05',
					'phrase' => 'international_volunteer_day',
				),
			);

			foreach ( array( $current_year, $current_year + 1 ) as $year ) {
				$year = absint( $year );

				foreach ( $definitions as $definition ) {
					$timestamp = false;

					if ( 'dynamic' === $definition['kind'] ) {
						$timestamp = strtotime( $definition['pattern'] . ' ' . $year . ' 12:00:00' );
					} else {
						$timestamp = strtotime( $year . '-' . $definition['date'] . ' 12:00:00' );
					}

					if ( false === $timestamp ) {
						continue;
					}

					$date = gmdate( 'Y-m-d', $timestamp );

					$items[] = array(
						'timestamp'  => (int) $timestamp,
						'date_label' => $this->format_weather_day_date( $date, $lang ),
						'title'      => $this->get_notable_day_phrase( $definition['phrase'], $lang ),
					);
				}
			}

			if ( empty( $items ) ) {
				return array(
					'date_label' => __( '—', 'noesia-smart-waiter' ),
					'title'      => $this->get_notable_day_phrase( 'no_notable_date_available', $lang ),
				);
			}

			usort(
				$items,
				static function ( $a, $b ) {
					return (int) $a['timestamp'] <=> (int) $b['timestamp'];
				}
			);

			$today_floor = strtotime( wp_date( 'Y-m-d 00:00:00', $current_timestamp ) );

			foreach ( $items as $item ) {
				if ( (int) $item['timestamp'] >= (int) $today_floor ) {
					return array(
						'date_label' => (string) $item['date_label'],
						'title'      => (string) $item['title'],
					);
				}
			}

			$first_item = reset( $items );

			return array(
				'date_label' => isset( $first_item['date_label'] ) ? (string) $first_item['date_label'] : __( '—', 'noesia-smart-waiter' ),
				'title'      => isset( $first_item['title'] ) ? (string) $first_item['title'] : $this->get_notable_day_phrase( 'no_notable_date_available', $lang ),
			);
		}

		/**
		 * Build a cached weather bundle for current weather + 7-day forecast.
		 *
		 * @param array<string,mixed> $options Settings array.
		 * @return array<string,mixed>
		 */

		public function get_weather_bundle( $options ) {
			$options         = is_array( $options ) ? $options : array();
			$weather_city    = isset( $options['weather_city'] ) ? sanitize_text_field( (string) $options['weather_city'] ) : '';
			$weather_country = isset( $options['weather_country'] ) ? $this->normalize_country_code( (string) $options['weather_country'] ) : '';
			$weather_lang    = $this->get_weather_lang( $options );
			$units_raw       = isset( $options['weather_units'] ) ? sanitize_key( (string) $options['weather_units'] ) : 'metric';
			$units           = in_array( $units_raw, array( 'metric', 'imperial' ), true ) ? $units_raw : 'metric';
			$units_label     = ( 'imperial' === $units ) ? '°F' : '°C';

			$bundle = array(
				'lang'             => $weather_lang,
				'place'            => '',
				'latitude'         => null,
				'longitude'        => null,
				'timezone'         => '',
				'units'            => $units,
				'units_label'      => $units_label,
				'now_label'        => '—',
				'condition'        => '',
				'tip'              => __( 'Have a great day.', 'noesia-smart-waiter' ),
				'today_label'      => $this->get_weather_ui_label( 'today', $weather_lang ),
				'min_text'         => $this->get_weather_ui_label( 'min', $weather_lang ),
				'max_text'         => $this->get_weather_ui_label( 'max', $weather_lang ),
				'rain_text'        => $this->get_weather_ui_label( 'rain', $weather_lang ),
				'today_min_label'  => '—',
				'today_max_label'  => '—',
				'today_rain_label' => '—',
				'sunrise_label'    => '—',
				'sunset_label'     => '—',
				'air_quality_index' => null,
				'air_quality_label' => 'AQI —',
				'fetched_at'       => 0,
				'days'             => array(),
				'error'            => '',
			);

			if ( '' === $weather_city ) {
				$bundle['error'] = __( 'Weather location not set yet.', 'noesia-smart-waiter' );
				return $bundle;
			}

			$cache_key = 'noesia_swa_dashboard_weather_' . md5( strtolower( $weather_city ) . '|' . strtolower( $weather_country ) . '|' . $units . '|' . $weather_lang );
			$cached    = get_transient( $cache_key );

			if ( is_array( $cached ) ) {
				$cached_has_ui_labels = isset(
					$cached['today_label'],
					$cached['min_text'],
					$cached['max_text'],
					$cached['rain_text'],
					$cached['latitude'],
					$cached['longitude'],
					$cached['sunrise_label'],
					$cached['sunset_label'],
					$cached['air_quality_label'],
					$cached['fetched_at']
				);
				if ( $cached_has_ui_labels ) {
					return $cached;
				}
			}

			$temp_unit = ( 'imperial' === $units ) ? 'fahrenheit' : 'celsius';
			$geo_args  = array(
				'name'     => $weather_city,
				'count'    => 8,
				'language' => $weather_lang,
			);

			if ( '' !== $weather_country ) {
				$geo_args['country'] = $weather_country;
			}

			$geo_url = add_query_arg( $geo_args, 'https://geocoding-api.open-meteo.com/v1/search' );
			$geo_res = wp_safe_remote_get(
				$geo_url,
				array(
					'timeout'   => 10,
					'sslverify' => true,
					'headers'   => array( 'Accept' => 'application/json' ),
				)
			);

			if ( is_wp_error( $geo_res ) ) {
				$bundle['error'] = __( 'Weather service is temporarily unavailable.', 'noesia-smart-waiter' );
				return $bundle;
			}

			$geo_body = json_decode( wp_remote_retrieve_body( $geo_res ), true );
			if ( ! is_array( $geo_body ) || empty( $geo_body['results'] ) || ! is_array( $geo_body['results'] ) ) {
				$bundle['error'] = __( 'Could not find the configured weather location.', 'noesia-smart-waiter' );
				return $bundle;
			}

			$match = $geo_body['results'][0];
			if ( '' !== $weather_country ) {
				foreach ( $geo_body['results'] as $row ) {
					if ( ! is_array( $row ) ) {
						continue;
					}
					$row_cc = isset( $row['country_code'] ) ? strtoupper( sanitize_key( (string) $row['country_code'] ) ) : '';
					if ( $row_cc === $weather_country ) {
						$match = $row;
						break;
					}
				}
			}

			$latitude  = isset( $match['latitude'] ) ? (float) $match['latitude'] : null;
			$longitude = isset( $match['longitude'] ) ? (float) $match['longitude'] : null;
			if ( null === $latitude || null === $longitude ) {
				$bundle['error'] = __( 'Weather location is incomplete.', 'noesia-smart-waiter' );
				return $bundle;
			}

			$place_name      = isset( $match['name'] ) ? sanitize_text_field( (string) $match['name'] ) : $weather_city;
			$place_cc        = isset( $match['country_code'] ) ? strtoupper( sanitize_key( (string) $match['country_code'] ) ) : $weather_country;
			$bundle['place'] = trim( $place_name . ( '' !== $place_cc ? ', ' . $place_cc : '' ) );
			$bundle['latitude']  = $latitude;
			$bundle['longitude'] = $longitude;
			$bundle['timezone']  = isset( $match['timezone'] ) ? sanitize_text_field( (string) $match['timezone'] ) : '';

						$forecast_url = add_query_arg(
				array(
					'latitude'                 => $latitude,
					'longitude'                => $longitude,
					'current_weather'          => 'true',
					'daily'                    => 'weathercode,temperature_2m_max,temperature_2m_min,precipitation_probability_max,windspeed_10m_max,windgusts_10m_max,sunrise,sunset',
					'timezone'                 => 'auto',
					'temperature_unit'         => $temp_unit,
					'windspeed_unit'           => 'kmh',
				),
				'https://api.open-meteo.com/v1/forecast'
			);

			$forecast_res = wp_safe_remote_get(
				$forecast_url,
				array(
					'timeout'   => 10,
					'sslverify' => true,
					'headers'   => array( 'Accept' => 'application/json' ),
				)
			);

			if ( is_wp_error( $forecast_res ) ) {
				$bundle['error'] = __( 'Forecast unavailable right now.', 'noesia-smart-waiter' );
				return $bundle;
			}

			$body = json_decode( wp_remote_retrieve_body( $forecast_res ), true );
			if ( ! is_array( $body ) ) {
				$bundle['error'] = __( 'Forecast unavailable right now.', 'noesia-smart-waiter' );
				return $bundle;
			}

					if ( ! empty( $body['current_weather'] ) && is_array( $body['current_weather'] ) ) {
				$current        = $body['current_weather'];
				$current_code   = isset( $current['weathercode'] ) ? absint( $current['weathercode'] ) : 0;
				$current_temp   = isset( $current['temperature'] ) ? round( (float) $current['temperature'] ) : null;
				$current_wind   = isset( $current['windspeed'] ) ? round( (float) $current['windspeed'] ) : null;
				$current_text   = ( null !== $current_temp ) ? (string) $current_temp . $units_label : '—';
				$wind_unit      = ' km/h';

				$bundle['now_label'] = trim( $this->get_weather_icon( $current_code ) . ' ' . $current_text );
				$bundle['condition'] = $this->get_weather_code_short_for_lang( $current_code, $weather_lang );

				if ( null !== $current_wind ) {
					$bundle['current_wind']       = $current_wind;
					$bundle['current_wind_label'] = (string) $current_wind . $wind_unit;
				}
			}

			if ( ! empty( $body['daily'] ) && is_array( $body['daily'] ) ) {
				$daily = $body['daily'];
								$times = isset( $daily['time'] ) && is_array( $daily['time'] ) ? $daily['time'] : array();
				$tmax  = isset( $daily['temperature_2m_max'] ) && is_array( $daily['temperature_2m_max'] ) ? $daily['temperature_2m_max'] : array();
				$tmin  = isset( $daily['temperature_2m_min'] ) && is_array( $daily['temperature_2m_min'] ) ? $daily['temperature_2m_min'] : array();
				$ppmx  = isset( $daily['precipitation_probability_max'] ) && is_array( $daily['precipitation_probability_max'] ) ? $daily['precipitation_probability_max'] : array();
				$wcod  = isset( $daily['weathercode'] ) && is_array( $daily['weathercode'] ) ? $daily['weathercode'] : array();
				$wmax  = isset( $daily['windspeed_10m_max'] ) && is_array( $daily['windspeed_10m_max'] ) ? $daily['windspeed_10m_max'] : array();
				$gmax  = isset( $daily['windgusts_10m_max'] ) && is_array( $daily['windgusts_10m_max'] ) ? $daily['windgusts_10m_max'] : array();
				$sunrise = isset( $daily['sunrise'] ) && is_array( $daily['sunrise'] ) ? $daily['sunrise'] : array();
				$sunset  = isset( $daily['sunset'] ) && is_array( $daily['sunset'] ) ? $daily['sunset'] : array();
				$out   = array();

				if ( isset( $sunrise[0] ) ) {
					$bundle['sunrise_label'] = $this->format_weather_time_label( (string) $sunrise[0] );
				}

				if ( isset( $sunset[0] ) ) {
					$bundle['sunset_label'] = $this->format_weather_time_label( (string) $sunset[0] );
				}

				for ( $index = 0; $index < 7; $index++ ) {
					$date = isset( $times[ $index ] ) ? sanitize_text_field( (string) $times[ $index ] ) : '';
					if ( '' === $date ) {
						continue;
					}

									$code = isset( $wcod[ $index ] ) ? absint( $wcod[ $index ] ) : 0;
					$max  = isset( $tmax[ $index ] ) ? round( (float) $tmax[ $index ] ) : null;
					$min  = isset( $tmin[ $index ] ) ? round( (float) $tmin[ $index ] ) : null;
					$pp   = isset( $ppmx[ $index ] ) ? absint( $ppmx[ $index ] ) : 0;
					$wind = isset( $wmax[ $index ] ) ? round( (float) $wmax[ $index ] ) : null;
					$gust = isset( $gmax[ $index ] ) ? round( (float) $gmax[ $index ] ) : null;

					$out[] = array(
						'date' => $date,
						'code' => $code,
						'max'  => $max,
						'min'  => $min,
						'pp'   => $pp,
						'wind' => $wind,
						'gust' => $gust,
					);

					if ( 0 === $index ) {
						$bundle['condition']         = $this->get_weather_code_short_for_lang( $code, $weather_lang );
						$bundle['today_min_label']   = ( null !== $min ) ? (string) $min . $units_label : '—';
						$bundle['today_max_label']   = ( null !== $max ) ? (string) $max . $units_label : '—';
						$bundle['today_rain_label']  = absint( $pp ) . '%';
						$bundle['today_wind_label']  = ( null !== $wind ) ? (string) $wind . ' km/h' : '—';
						$bundle['today_gust_label']  = ( null !== $gust ) ? (string) $gust . ' km/h' : '—';
						$bundle['today_wind_value']  = $wind;
						$bundle['today_gust_value']  = $gust;
					}
				}

				$bundle['days'] = $out;
			}

			$air_quality_index = $this->get_current_air_quality_index( $latitude, $longitude );
			if ( null !== $air_quality_index ) {
				$bundle['air_quality_index'] = $air_quality_index;
			}
			$bundle['air_quality_label'] = $this->get_air_quality_label( $bundle['air_quality_index'] );
			$bundle['fetched_at']        = time();

			set_transient( $cache_key, $bundle, 15 * MINUTE_IN_SECONDS );
			return $bundle;
		}


		/**
		 * Prepare dashboard-friendly forecast rows.
		 *
		 * @param array<string,mixed> $weather_bundle Weather bundle.
		 * @return array<int,array<string,mixed>>
		 */

		public function get_forecast_days( $weather_bundle ) {
			$weather_bundle = is_array( $weather_bundle ) ? $weather_bundle : array();
			$days           = isset( $weather_bundle['days'] ) && is_array( $weather_bundle['days'] ) ? $weather_bundle['days'] : array();
			$units_label    = isset( $weather_bundle['units_label'] ) ? (string) $weather_bundle['units_label'] : '';
			$lang           = isset( $weather_bundle['lang'] ) ? sanitize_key( (string) $weather_bundle['lang'] ) : 'en';
			$output         = array();

			foreach ( $days as $index => $day ) {
				if ( ! is_array( $day ) || 0 === (int) $index ) {
					continue;
				}

				$date = isset( $day['date'] ) ? sanitize_text_field( (string) $day['date'] ) : '';
				$code = isset( $day['code'] ) ? absint( $day['code'] ) : 0;
				$pp   = isset( $day['pp'] ) ? absint( $day['pp'] ) : 0;
				$max  = ( isset( $day['max'] ) && null !== $day['max'] ) ? (string) $day['max'] . $units_label : '—';
				$min  = ( isset( $day['min'] ) && null !== $day['min'] ) ? (string) $day['min'] . $units_label : '—';

				$output[] = array(
					'date'      => $date,
					'day_name'  => ( '' !== $date ) ? $this->format_weather_day_name( $date, $lang ) : '—',
					'day_date'  => ( '' !== $date ) ? $this->format_weather_day_date( $date, $lang ) : '—',
					'code'      => $code,
					'icon'      => $this->get_weather_icon( $code ),
					'short'     => $this->get_weather_code_short_for_lang( $code, $lang ),
					'max_label' => $max,
					'min_label' => $min,
					'pp'        => $pp,
					'rain_label' => $this->get_weather_ui_label( 'rain', $lang ) . ' ' . absint( $pp ) . '%',
				);

				if ( count( $output ) >= 5 ) {
					break;
				}
			}

			return $output;
		}
    }
}
