<?php
/**
 * File Path: includes/admin/tabs/general/class-mbs-timezone-helper.php
 *
 * MBS Timezone Utilities — static helper extracted from MBS_Tab_General_Currency (R-01).
 *
 * Provides five public static methods used by the General tab timezone field
 * and by any future Sprint GC2 date-formatting consumers.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Admin/Tabs/General
 * @since      1.0.0
 */

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

class MBS_Timezone_Helper { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Translate a UTC offset string (as returned by wp_timezone_string() for
	 * sites configured by offset rather than named zone) to a representative
	 * IANA timezone identifier.
	 *
	 * The table maps both the ±HH:MM and the short ±HH forms that WordPress
	 * may emit. When no match is found (should be rare) we return 'UTC' as the
	 * safe last resort.
	 *
	 * F-009: This method exists specifically to fix the edge case noted in the
	 * Phase 4b docblock — wp_timezone_string() can return "+05:30" which is not
	 * in timezone_identifiers_list(), causing a silent fall-through to UTC.
	 *
	 * @param string $offset UTC offset string, e.g. "+05:30", "-07", "+00:00".
	 * @return string IANA timezone identifier, e.g. "Asia/Kolkata". 'UTC' on no match.
	 */
	public static function offset_to_iana( string $offset ): string {
		$map = array(
			// UTC
			'+00:00' => 'UTC',
			'+00'    => 'UTC',
			// UTC+1
			'+01:00' => 'Europe/Paris',
			'+01'    => 'Europe/Paris',
			// UTC+2
			'+02:00' => 'Europe/Helsinki',
			'+02'    => 'Europe/Helsinki',
			// UTC+3
			'+03:00' => 'Europe/Moscow',
			'+03'    => 'Europe/Moscow',
			// UTC+3:30
			'+03:30' => 'Asia/Tehran',
			// UTC+4
			'+04:00' => 'Asia/Dubai',
			'+04'    => 'Asia/Dubai',
			// UTC+4:30
			'+04:30' => 'Asia/Kabul',
			// UTC+5
			'+05:00' => 'Asia/Karachi',
			'+05'    => 'Asia/Karachi',
			// UTC+5:30
			'+05:30' => 'Asia/Kolkata',
			// UTC+5:45
			'+05:45' => 'Asia/Kathmandu',
			// UTC+6
			'+06:00' => 'Asia/Dhaka',
			'+06'    => 'Asia/Dhaka',
			// UTC+6:30
			'+06:30' => 'Asia/Yangon',
			// UTC+7
			'+07:00' => 'Asia/Bangkok',
			'+07'    => 'Asia/Bangkok',
			// UTC+8
			'+08:00' => 'Asia/Shanghai',
			'+08'    => 'Asia/Shanghai',
			// UTC+8:45
			'+08:45' => 'Australia/Eucla',
			// UTC+9
			'+09:00' => 'Asia/Tokyo',
			'+09'    => 'Asia/Tokyo',
			// UTC+9:30
			'+09:30' => 'Australia/Darwin',
			// UTC+10
			'+10:00' => 'Australia/Sydney',
			'+10'    => 'Australia/Sydney',
			// UTC+10:30
			'+10:30' => 'Australia/Lord_Howe',
			// UTC+11
			'+11:00' => 'Pacific/Noumea',
			'+11'    => 'Pacific/Noumea',
			// UTC+12
			'+12:00' => 'Pacific/Auckland',
			'+12'    => 'Pacific/Auckland',
			// UTC+12:45
			'+12:45' => 'Pacific/Chatham',
			// UTC+13
			'+13:00' => 'Pacific/Apia',
			'+13'    => 'Pacific/Apia',
			// UTC+14
			'+14:00' => 'Pacific/Kiritimati',
			'+14'    => 'Pacific/Kiritimati',
			// UTC-1
			'-01:00' => 'Atlantic/Azores',
			'-01'    => 'Atlantic/Azores',
			// UTC-2
			'-02:00' => 'America/Noronha',
			'-02'    => 'America/Noronha',
			// UTC-2:30
			'-02:30' => 'America/St_Johns',
			// UTC-3
			'-03:00' => 'America/Sao_Paulo',
			'-03'    => 'America/Sao_Paulo',
			// UTC-3:30
			'-03:30' => 'America/St_Johns',
			// UTC-4
			'-04:00' => 'America/Halifax',
			'-04'    => 'America/Halifax',
			// UTC-5
			'-05:00' => 'America/New_York',
			'-05'    => 'America/New_York',
			// UTC-6
			'-06:00' => 'America/Chicago',
			'-06'    => 'America/Chicago',
			// UTC-7
			'-07:00' => 'America/Denver',
			'-07'    => 'America/Denver',
			// UTC-8
			'-08:00' => 'America/Los_Angeles',
			'-08'    => 'America/Los_Angeles',
			// UTC-9
			'-09:00' => 'America/Anchorage',
			'-09'    => 'America/Anchorage',
			// UTC-9:30
			'-09:30' => 'Pacific/Marquesas',
			// UTC-10
			'-10:00' => 'Pacific/Honolulu',
			'-10'    => 'Pacific/Honolulu',
			// UTC-11
			'-11:00' => 'Pacific/Midway',
			'-11'    => 'Pacific/Midway',
			// UTC-12
			'-12:00' => 'Etc/GMT+12',
			'-12'    => 'Etc/GMT+12',
		);

		return $map[ $offset ] ?? 'UTC';
	}

	/**
	 * Get the WordPress site timezone as a valid IANA identifier.
	 *
	 * When WordPress is configured by UTC offset (e.g. "+05:30") rather than
	 * a named zone, delegates to offset_to_iana() to produce a representative
	 * IANA zone. Used by MBS_Tab_General::enqueue() via wp_localize_script.
	 *
	 * @return string IANA timezone identifier. 'UTC' on fallback.
	 */
	public static function get_wp_iana_timezone(): string {
		$wp_tz       = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : '';
		$valid_zones = timezone_identifiers_list();

		if ( '' !== $wp_tz && in_array( $wp_tz, $valid_zones, true ) ) {
			return $wp_tz;
		}

		if ( '' !== $wp_tz ) {
			return self::offset_to_iana( $wp_tz );
		}

		return 'UTC';
	}

	/**
	 * Map a WordPress locale string to an IANA timezone region prefix.
	 *
	 * Country code takes priority over language prefix. Returns '' when the
	 * locale is not in either table so callers can fall back to the flat list.
	 *
	 * F-008: 70-entry ISO 3166-1 alpha-2 country table + 50-entry language
	 * prefix fallback table.
	 *
	 * @param string $locale WordPress locale string (e.g. 'fr_FR', 'ja', 'en_US').
	 * @return string IANA region prefix (e.g. 'Europe/', 'Asia/') or '' on no match.
	 */
	public static function locale_to_region_prefix( string $locale ): string {
		// Country-code → IANA prefix (ISO 3166-1 alpha-2, 70 entries).
		$country_map = array(
			'AD' => 'Europe/',   'AE' => 'Asia/',     'AF' => 'Asia/',     'AL' => 'Europe/',
			'AM' => 'Asia/',     'AO' => 'Africa/',   'AR' => 'America/',  'AT' => 'Europe/',
			'AU' => 'Australia/','AZ' => 'Asia/',     'BA' => 'Europe/',   'BD' => 'Asia/',
			'BE' => 'Europe/',   'BG' => 'Europe/',   'BR' => 'America/',  'BY' => 'Europe/',
			'CA' => 'America/',  'CH' => 'Europe/',   'CL' => 'America/',  'CN' => 'Asia/',
			'CO' => 'America/',  'CZ' => 'Europe/',   'DE' => 'Europe/',   'DK' => 'Europe/',
			'DZ' => 'Africa/',   'EG' => 'Africa/',   'ES' => 'Europe/',   'ET' => 'Africa/',
			'FI' => 'Europe/',   'FR' => 'Europe/',   'GB' => 'Europe/',   'GH' => 'Africa/',
			'GR' => 'Europe/',   'HK' => 'Asia/',     'HR' => 'Europe/',   'HU' => 'Europe/',
			'ID' => 'Asia/',     'IE' => 'Europe/',   'IL' => 'Asia/',     'IN' => 'Asia/',
			'IQ' => 'Asia/',     'IR' => 'Asia/',     'IT' => 'Europe/',   'JP' => 'Asia/',
			'KE' => 'Africa/',   'KR' => 'Asia/',     'KW' => 'Asia/',     'KZ' => 'Asia/',
			'LB' => 'Asia/',     'LY' => 'Africa/',   'MA' => 'Africa/',   'MX' => 'America/',
			'MY' => 'Asia/',     'NG' => 'Africa/',   'NL' => 'Europe/',   'NO' => 'Europe/',
			'NZ' => 'Pacific/',  'PE' => 'America/',  'PH' => 'Asia/',     'PK' => 'Asia/',
			'PL' => 'Europe/',   'PT' => 'Europe/',   'RO' => 'Europe/',   'RS' => 'Europe/',
			'RU' => 'Europe/',   'SA' => 'Asia/',     'SE' => 'Europe/',   'SG' => 'Asia/',
			'ZA' => 'Africa/',
		);

		// Language-prefix → IANA prefix (50 entries).
		$lang_map = array(
			'af' => 'Africa/',   'am' => 'Africa/',   'ar' => 'Asia/',     'az' => 'Asia/',
			'be' => 'Europe/',   'bg' => 'Europe/',   'bn' => 'Asia/',     'bs' => 'Europe/',
			'ca' => 'Europe/',   'cs' => 'Europe/',   'cy' => 'Europe/',   'da' => 'Europe/',
			'de' => 'Europe/',   'el' => 'Europe/',   'en' => 'America/',  'es' => 'Europe/',
			'et' => 'Europe/',   'fa' => 'Asia/',     'fi' => 'Europe/',   'fr' => 'Europe/',
			'gl' => 'Europe/',   'gu' => 'Asia/',     'he' => 'Asia/',     'hi' => 'Asia/',
			'hr' => 'Europe/',   'hu' => 'Europe/',   'hy' => 'Asia/',     'id' => 'Asia/',
			'is' => 'Atlantic/', 'it' => 'Europe/',   'ja' => 'Asia/',     'ka' => 'Asia/',
			'kk' => 'Asia/',     'kn' => 'Asia/',     'ko' => 'Asia/',     'lt' => 'Europe/',
			'lv' => 'Europe/',   'mk' => 'Europe/',   'ml' => 'Asia/',     'mn' => 'Asia/',
			'mr' => 'Asia/',     'ms' => 'Asia/',     'nb' => 'Europe/',   'ne' => 'Asia/',
			'nl' => 'Europe/',   'nn' => 'Europe/',   'pl' => 'Europe/',   'pt' => 'Europe/',
			'ro' => 'Europe/',   'sq' => 'Europe/',
		);

		// Country code has priority — parse underscore-suffix locale.
		if ( str_contains( $locale, '_' ) ) {
			$country = strtoupper( explode( '_', $locale, 2 )[1] );
			if ( isset( $country_map[ $country ] ) ) {
				return $country_map[ $country ];
			}
		}

		// Fall back to bare language prefix.
		$lang = strtolower( explode( '_', $locale, 2 )[0] );
		return $lang_map[ $lang ] ?? '';
	}

	/**
	 * Map an IANA timezone region prefix to a human-readable optgroup label.
	 *
	 * @param string $prefix IANA prefix, e.g. 'America/', 'Europe/'.
	 * @return string Translated label, or the prefix without trailing slash on no match.
	 */
	public static function region_prefix_to_label( string $prefix ): string {
		$labels = array(
			'Africa/'    => __( 'Africa',       'my-book-showroom' ),
			'America/'   => __( 'Americas',     'my-book-showroom' ),
			'Antarctica/'=> __( 'Antarctica',   'my-book-showroom' ),
			'Arctic/'    => __( 'Arctic',       'my-book-showroom' ),
			'Asia/'      => __( 'Asia',         'my-book-showroom' ),
			'Atlantic/'  => __( 'Atlantic',     'my-book-showroom' ),
			'Australia/' => __( 'Australia',    'my-book-showroom' ),
			'Europe/'    => __( 'Europe',       'my-book-showroom' ),
			'Indian/'    => __( 'Indian Ocean', 'my-book-showroom' ),
			'Pacific/'   => __( 'Pacific',      'my-book-showroom' ),
		);
		return $labels[ $prefix ] ?? rtrim( $prefix, '/' );
	}

	/**
	 * Resolve the timezone value to pre-populate the Timezone dropdown with.
	 *
	 * Priority (client directive 2026-04-19 — never hard-code UTC):
	 *   1. User's saved MBS timezone (if valid IANA)
	 *   2. WordPress's timezone_string (if valid IANA)
	 *   3. offset_to_iana() translation when WP returns an offset string (F-009)
	 *   4. 'UTC' (last-resort fallback only)
	 *
	 * @return string IANA timezone identifier.
	 */
	public static function resolve_current_timezone(): string {
		$valid_zones = timezone_identifiers_list();

		$saved = get_option( 'mbs_setting_general_timezone', '' );
		if ( '' !== $saved && in_array( $saved, $valid_zones, true ) ) {
			return $saved;
		}

		$wp_tz = function_exists( 'wp_timezone_string' ) ? wp_timezone_string() : '';
		if ( '' !== $wp_tz ) {
			if ( in_array( $wp_tz, $valid_zones, true ) ) {
				return $wp_tz;
			}
			// F-009: offset string — translate to representative IANA zone.
			$iana = self::offset_to_iana( $wp_tz );
			if ( 'UTC' !== $iana || str_starts_with( $wp_tz, '+00' ) ) {
				return $iana;
			}
		}

		return 'UTC';
	}
}
