// CONTRACT:
// Locale bundles are standalone and loaded on demand. Adding a locale requires
// updating Locale, AVAILABLE_LOCALES, browser-language detection, localeLoaders,
// scripts/i18n-utils.cjs, and the locale file itself.
//
// Do not call t() at module scope for exported arrays/constants; async init can
// otherwise leave raw txt_* keys in the rendered UI.
export type Locale =
  | 'en'
  | 'zh-CN'
  | 'zh-TW'
  | 'ru'
  | 'es'
  | 'fi'
  | 'de'
  | 'fr'
  | 'it'
  | 'sv';

import enMessages from './i18n/locales/en';
const LOCALE_STORAGE_KEY = 'nodewarden.locale';

type MessageTable = Record<string, string>;

export const AVAILABLE_LOCALES: readonly { value: Locale; label: string }[] = [
  { value: 'en', label: 'English' },
  { value: 'zh-CN', label: '简体中文' },
  { value: 'zh-TW', label: '繁體中文' },
  { value: 'ru', label: 'Русский' },
  { value: 'es', label: 'Español' },
  { value: 'fi', label: 'Suomi' },
  { value: 'de', label: 'Deutsch' },
  { value: 'fr', label: 'Français' },
  { value: 'it', label: 'Italiano' },
  { value: 'sv', label: 'Svenska' },
];

let locale: Locale = resolveInitialLocale();
let activeMessages: MessageTable = enMessages;
const loadedMessages = new Map<Locale, MessageTable>([['en', enMessages]]);

function isLocale(value: unknown): value is Locale {
  return AVAILABLE_LOCALES.some((item) => item.value === value);
}

function resolveInitialLocale(): Locale {
  try {
    const saved = localStorage.getItem(LOCALE_STORAGE_KEY);
    if (isLocale(saved)) return saved;
  } catch {
    // ignore storage errors
  }
  if (typeof navigator !== 'undefined') {
    const langs = Array.isArray(navigator.languages) ? navigator.languages : [navigator.language];
    for (const lang of langs) {
      const normalized = String(lang || '').toLowerCase();
      if (normalized === 'zh-tw' || normalized === 'zh-hk' || normalized === 'zh-mo' || normalized.includes('hant')) return 'zh-TW';
      if (normalized.startsWith('zh')) return 'zh-CN';
      if (normalized.startsWith('ru')) return 'ru';
      if (normalized.startsWith('es')) return 'es';
      if (normalized.startsWith('fi')) return 'fi';
      if (normalized.startsWith('de')) return 'de';
      if (normalized.startsWith('fr')) return 'fr';
      if (normalized.startsWith('it')) return 'it';
      if (normalized.startsWith('sv')) return 'sv';
    }
  }
  return 'en';
}

const localeLoaders: Record<Locale, () => Promise<{ default: MessageTable }>> = {
  en: () => Promise.resolve({ default: enMessages }),
  'zh-CN': () => import('./i18n/locales/zh-CN'),
  'zh-TW': () => import('./i18n/locales/zh-TW'),
  ru: () => import('./i18n/locales/ru'),
  es: () => import('./i18n/locales/es'),
  fi: () => import('./i18n/locales/fi'),
  de: () => import('./i18n/locales/de'),
  fr: () => import('./i18n/locales/fr'),
  it: () => import('./i18n/locales/it'),
  sv: () => import('./i18n/locales/sv'),
};

function localeToHtmlLang(value: Locale): string {
  return value;
}

function syncDocumentLanguage(): void {
  if (typeof document === 'undefined') return;
  document.documentElement.lang = localeToHtmlLang(locale);
}

async function loadLocaleMessages(next: Locale): Promise<MessageTable> {
  const cached = loadedMessages.get(next);
  if (cached) return cached;
  const mod = await localeLoaders[next]();
  loadedMessages.set(next, mod.default);
  return mod.default;
}

async function loadFallbackMessages(): Promise<MessageTable> {
  return enMessages;
}

export type I18nParams = Record<string, string | number | null | undefined>;

export async function initI18n(): Promise<void> {
  try {
    activeMessages = await loadLocaleMessages(locale);
  } catch (error) {
    console.error('Failed to load locale, falling back to English:', error);
    locale = 'en';
    activeMessages = await loadFallbackMessages();
  } finally {
    syncDocumentLanguage();
  }
}

export function t(key: string, params?: I18nParams): string {
  const template = activeMessages[key] ?? key;
  if (!params) return template;
  return template.replace(/\{(\w+)\}/g, (_, name: string) => String(params[name] ?? ''));
}

export function translateServerError(message: string | null | undefined, fallback: string): string {
  const normalized = String(message || '').trim();
  if (!normalized) return fallback;

  const rateLimitMatch = normalized.match(/^Rate limit exceeded\. Try again in (\d+) seconds\.$/i);
  if (rateLimitMatch) {
    return t('txt_rate_limit_try_again_seconds', { seconds: rateLimitMatch[1] });
  }

  const backupDestinationLimitMatch = normalized.match(/^You can save up to (\d+) backup destinations$/i);
  if (backupDestinationLimitMatch) {
    return t('txt_backup_error_destination_limit', { count: backupDestinationLimitMatch[1] });
  }

  const backupArchiveVerificationMatch = normalized.match(/^Backup archive upload verification failed after (\d+) attempts: (.+)$/i);
  if (backupArchiveVerificationMatch) {
    return t('txt_backup_error_archive_upload_verification_failed_attempts', {
      count: backupArchiveVerificationMatch[1],
      reason: translateServerError(backupArchiveVerificationMatch[2], backupArchiveVerificationMatch[2]),
    });
  }

  const remoteAttachmentStatusMatch = normalized.match(/^Remote attachment (download|batch download) failed: (\d+)$/i);
  if (remoteAttachmentStatusMatch) {
    return t(
      remoteAttachmentStatusMatch[1].toLowerCase() === 'batch download'
        ? 'txt_backup_error_remote_attachment_batch_download_failed_status'
        : 'txt_backup_error_remote_attachment_download_failed_status',
      { status: remoteAttachmentStatusMatch[2] }
    );
  }

  const providerStatusMatch = normalized.match(/^(WebDAV|S3) (directory creation|upload|listing|download|delete|existence check) failed: (\d+)$/i);
  if (providerStatusMatch) {
    const provider = providerStatusMatch[1].toLowerCase() === 'webdav' ? 'webdav' : 's3';
    const actionKey = providerStatusMatch[2].toLowerCase().replace(/\s+/g, '_');
    return t(`txt_backup_error_${provider}_${actionKey}_failed_status`, { status: providerStatusMatch[3] });
  }

  const key = {
    'Account is disabled': 'txt_server_error_account_disabled',
    'Another backup or restore run is already in progress': 'txt_backup_error_another_backup_or_restore_running',
    'Another backup run is already in progress': 'txt_backup_error_another_backup_running',
    'Backup archive upload failed': 'txt_backup_error_archive_upload_failed',
    'Backup attachment blob is invalid': 'txt_backup_error_attachment_blob_invalid',
    'Backup attachment blob is required': 'txt_backup_error_attachment_blob_required',
    'Backup attachment blob not found': 'txt_backup_error_attachment_blob_not_found',
    'Backup attachment download failed': 'txt_backup_error_attachment_download_failed',
    'Backup destination is invalid': 'txt_backup_error_destination_invalid',
    'Backup destination not found': 'txt_backup_error_destination_not_found',
    'Backup destination ids must be unique': 'txt_backup_error_destination_ids_unique',
    'Backup destination type is invalid': 'txt_backup_error_destination_type_invalid',
    'Backup destinations are invalid': 'txt_backup_error_destinations_invalid',
    'Backup export payload is invalid': 'txt_backup_error_export_payload_invalid',
    'Backup file checksum does not match its filename': 'txt_backup_error_file_checksum_mismatch',
    'Backup file is required': 'txt_backup_error_file_required',
    'Backup interval hours must be between 1 and 99': 'txt_backup_error_interval_hours_range',
    'Backup retention count must be between 1 and 1000': 'txt_backup_error_retention_count_range',
    'Backup run failed': 'txt_backup_error_run_failed',
    'Backup run payload is invalid': 'txt_backup_error_run_payload_invalid',
    'Backup run response is invalid': 'txt_backup_error_run_response_invalid',
    'Backup settings are invalid': 'txt_backup_error_settings_invalid',
    'Backup settings could not be loaded': 'txt_backup_error_settings_load_failed',
    'Backup settings envelope is invalid': 'txt_backup_error_settings_envelope_invalid',
    'Backup settings need administrator reactivation after restore': 'txt_backup_error_settings_need_reactivation',
    'Backup settings payload is invalid': 'txt_backup_error_settings_payload_invalid',
    'Backup settings repair payload is invalid': 'txt_backup_error_settings_repair_payload_invalid',
    'Backup settings repair state could not be loaded': 'txt_backup_error_settings_repair_state_load_failed',
    'Backup start time must be in HH:mm format': 'txt_backup_error_start_time_format',
    'Client IP is required': 'txt_server_error_client_ip_required',
    'ClientId or clientSecret is incorrect. Try again': 'txt_server_error_client_credentials_incorrect',
    'Content-Type must be multipart/form-data': 'txt_backup_error_multipart_required',
    'Email already registered': 'txt_server_error_email_already_registered',
    'Email and password are required': 'txt_server_error_email_password_required',
    'Email is required': 'txt_server_error_email_required',
    'Forbidden': 'txt_server_error_forbidden',
    'Invite code is invalid or expired': 'txt_server_error_invite_invalid_or_expired',
    'Invite code is required': 'txt_server_error_invite_required',
    'Invalid backup timezone': 'txt_backup_error_timezone_invalid',
    'Invalid password': 'txt_server_error_invalid_password',
    'Invalid refresh token': 'txt_server_error_invalid_refresh_token',
    'Invalid remote backup path': 'txt_backup_error_remote_path_invalid',
    'Invalid request payload': 'txt_server_error_invalid_request_payload',
    'Invalid user verification token': 'txt_server_error_invalid_user_verification_token',
    'JWT_SECRET is not set': 'txt_server_error_jwt_secret_missing',
    'JWT_SECRET is using the default/sample value. Please change it.': 'txt_server_error_jwt_secret_default',
    'JWT_SECRET must be at least 32 characters': 'txt_server_error_jwt_secret_too_short',
    'Parameter error': 'txt_server_error_parameter_error',
    'Please select a backup file': 'txt_backup_error_select_backup_file',
    'Please select a backup ZIP file': 'txt_backup_error_select_backup_zip_file',
    'Refresh token is required': 'txt_server_error_refresh_token_required',
    'Remote backup ZIP checksum verification failed': 'txt_backup_error_remote_zip_checksum_failed',
    'Remote backup ZIP size verification failed': 'txt_backup_error_remote_zip_size_failed',
    'Remote backup delete failed': 'txt_backup_error_remote_delete_failed',
    'Remote backup download failed': 'txt_backup_error_remote_download_failed',
    'Remote backup download payload is invalid': 'txt_backup_error_remote_download_payload_invalid',
    'Remote backup integrity inspection failed': 'txt_backup_error_remote_integrity_failed',
    'Remote backup listing failed': 'txt_backup_error_remote_listing_failed',
    'Remote restore payload is invalid': 'txt_backup_error_remote_restore_payload_invalid',
    'Registration is temporarily unavailable, retry once': 'txt_server_error_registration_retry',
    'S3 access key is required': 'txt_backup_error_s3_access_key_required',
    'S3 bucket is required': 'txt_backup_error_s3_bucket_required',
    'S3 endpoint is required': 'txt_backup_error_s3_endpoint_required',
    'S3 endpoint must start with http:// or https://': 'txt_backup_error_s3_endpoint_protocol',
    'S3 secret key is required': 'txt_backup_error_s3_secret_key_required',
    'TOTP token is required': 'txt_server_error_totp_token_required',
    'Two factor required.': 'txt_server_error_two_factor_required',
    'Two-step token is invalid. Try again.': 'txt_server_error_two_factor_invalid',
    'Unable to read backup file': 'txt_backup_error_read_backup_file_failed',
    'Unsupported backup destination type': 'txt_backup_error_destination_type_unsupported',
    'Username or password is incorrect. Try again': 'txt_server_error_username_password_incorrect',
    'WebDAV password is required': 'txt_backup_error_webdav_password_required',
    'WebDAV remote backup path is too deep for safe attachment batching': 'txt_backup_error_webdav_path_too_deep',
    'WebDAV server URL is required': 'txt_backup_error_webdav_url_required',
    'WebDAV server URL must start with http:// or https://': 'txt_backup_error_webdav_url_protocol',
    'WebDAV username is required': 'txt_backup_error_webdav_username_required',
    'Secure browser cryptography is unavailable. Open NodeWarden over HTTPS in a supported browser.': 'txt_web_crypto_unavailable',
    'masterPasswordHash is required': 'txt_server_error_master_password_hash_required',
    'masterPasswordHash or userVerificationToken is required': 'txt_server_error_master_password_or_verification_required',
  }[normalized];
  return key ? t(key) : normalized;
}

export function getLocale(): Locale {
  return locale;
}

export async function setLocale(next: Locale): Promise<void> {
  let nextMessages: MessageTable;
  try {
    nextMessages = await loadLocaleMessages(next);
  } catch (error) {
    console.error('Failed to load selected locale, falling back to English:', error);
    next = 'en';
    nextMessages = await loadFallbackMessages();
  }
  locale = next;
  activeMessages = nextMessages;
  syncDocumentLanguage();
  try {
    localStorage.setItem(LOCALE_STORAGE_KEY, next);
  } catch {
    // ignore storage errors
  }
}
