import "dart:convert";

import "package:flutter/services.dart" show rootBundle;
import "package:intl/intl.dart";

import "main.dart" show appSettings;

/// UI translation for the phone app. Every user-visible string lives in a
/// per-language JSON file under assets/i18n/; nothing hardcodes English.
///
/// The language is the language half of the signed-in user's stored BCP-47
/// locale (appSettings.locale), the same value that already drives dates and
/// numbers in format.dart — so the Settings language picker steers both. Because
/// MaterialApp is wrapped in a ListenableBuilder on appSettings, changing the
/// locale rebuilds the whole tree and every t() re-resolves.
///
/// Deliberately hand-rolled rather than gen_l10n/ARB: the app already reads a
/// runtime locale from the profile (not the device locale Flutter's own
/// localization is built around), and "a JSON file per language the app reads
/// from" is exactly the shape asked for.

const String _base = "en";

/// Languages that ship a real translation file. The Settings picker offers every
/// European language for date/number formatting; only these change the UI text,
/// and the rest fall back to English.
const List<String> translatedLanguages = ["en", "pl", "da"];

final Map<String, Map<String, dynamic>> _messages = {};

/// Loads every bundled language file into memory. Call once in main() before
/// runApp, alongside initializeDateFormatting — the files are small assets and
/// t() is synchronous thereafter.
Future<void> loadTranslations() async {
  for (final code in translatedLanguages) {
    final raw = await rootBundle.loadString("assets/i18n/$code.json");
    _messages[code] = json.decode(raw) as Map<String, dynamic>;
  }
}

String get _lang {
  final lang = appSettings.locale.split("-").first;
  return _messages.containsKey(lang) ? lang : _base;
}

/// True when the active language has its own file (vs. falling back to English).
bool get languageTranslated =>
    _messages.containsKey(appSettings.locale.split("-").first);

dynamic _lookup(Map<String, dynamic>? dict, String key) {
  dynamic node = dict;
  for (final part in key.split(".")) {
    if (node is! Map) return null;
    node = node[part];
  }
  return node;
}

String _interpolate(String template, Map<String, Object?>? params) {
  if (params == null) return template;
  return template.replaceAllMapped(RegExp(r"\{(\w+)\}"), (m) {
    final v = params[m.group(1)];
    return v == null ? m.group(0)! : v.toString();
  });
}

/// Translate [key] for the active language.
///
/// Pass [n] to select a plural form (a JSON object keyed by CLDR category —
/// one/few/many/other); Intl.plural picks the right one for the language, which
/// is what makes Polish read correctly rather than an English-style n==1 split.
/// Any [params] interpolate `{name}` placeholders. A key missing from the active
/// language falls back to English; missing from English too, the key is returned
/// so the gap is visible rather than blank.
String t(String key, {Map<String, Object?>? params, int? n}) {
  final lang = _lang;
  dynamic entry = _lookup(_messages[lang], key);
  entry ??= _lookup(_messages[_base], key);
  if (entry == null) return key;

  if (entry is Map) {
    if (n == null) return key;
    final forms = entry;
    entry = Intl.plural(
      n,
      one: forms["one"] as String?,
      few: forms["few"] as String?,
      many: forms["many"] as String?,
      other: (forms["other"] ?? forms["one"]) as String? ?? key,
      locale: lang,
    );
  }
  if (entry is! String) return key;

  final merged = <String, Object?>{if (n != null) "n": n, ...?params};
  return _interpolate(entry, merged.isEmpty ? null : merged);
}
