/// Locale-aware selection of Tesseract OCR language packs.
///
/// Tane is international by design: the OCR must read seed packets in whatever
/// language the user runs the app, not a fixed `eng`/`spa`. This maps the app's
/// preferred locales to the traineddata packs we actually bundle and degrades
/// gracefully — an unbundled locale simply falls back to the Latin baseline
/// instead of failing. As more packs are bundled (incl. RTL/CJK scripts) they
/// light up automatically with no code change here.
library;

import 'dart:convert';

const _trainedDataSuffix = '.traineddata';

/// ISO 639-1 (locale) → Tesseract language code (mostly ISO 639-2/T). Covers the
/// scripts we plausibly bundle; extend as new packs are added. Kept
/// intentionally broad — international first, not one region.
const Map<String, String> isoToTesseract = {
  // Latin-script European
  'en': 'eng',
  'es': 'spa',
  'ca': 'cat',
  'gl': 'glg',
  'eu': 'eus',
  'pt': 'por',
  'fr': 'fra',
  'de': 'deu',
  'it': 'ita',
  'nl': 'nld',
  'pl': 'pol',
  'cs': 'ces',
  'ro': 'ron',
  'sv': 'swe',
  'da': 'dan',
  'fi': 'fin',
  'tr': 'tur',
  // Cyrillic / Greek
  'ru': 'rus',
  'uk': 'ukr',
  'el': 'ell',
  // RTL scripts
  'ar': 'ara',
  'he': 'heb',
  'fa': 'fas',
  'ur': 'urd',
  // CJK & other non-Latin
  'zh': 'chi_sim',
  'ja': 'jpn',
  'ko': 'kor',
  'hi': 'hin',
  'th': 'tha',
};

/// Parses the bundled OCR config (`assets/tessdata_config.json`) into the set of
/// available Tesseract language codes (basenames, e.g. `eng`, `spa`). The config
/// is the single source of truth for what is shipped, so this stays in sync with
/// the assets automatically.
Set<String> parseAvailableOcrLanguages(String configJson) {
  final data = jsonDecode(configJson) as Map<String, dynamic>;
  final files = (data['files'] as List?)?.cast<String>() ?? const [];
  return files
      .map(
        (f) => f.endsWith(_trainedDataSuffix)
            ? f.substring(0, f.length - _trainedDataSuffix.length)
            : f,
      )
      .where((code) => code.isNotEmpty)
      .toSet();
}

/// Resolves the `+`-joined Tesseract language string for [preferredLocales]
/// (ordered most-preferred first; each an ISO 639-1 code or a fuller tag like
/// `es_ES`/`zh-Hans`), limited to the [available] bundled packs.
///
/// Rules, in order:
///  1. Each preferred locale that maps to an available pack is included, in
///     order, de-duplicated.
///  2. The Latin [baseline] (`eng`) is appended if bundled — Latin text is
///     common on packets even in non-Latin locales, and it's a safe default.
///  3. If nothing matched and the baseline is unavailable, fall back to every
///     available pack (sorted) so OCR still runs with whatever we shipped.
///
/// Returns an empty string only when [available] is empty.
String resolveOcrLanguages({
  required List<String> preferredLocales,
  required Set<String> available,
  String baseline = 'eng',
}) {
  final ordered = <String>[];
  for (final locale in preferredLocales) {
    final code = tesseractCodeForLocale(locale);
    if (code != null && available.contains(code) && !ordered.contains(code)) {
      ordered.add(code);
    }
  }
  if (available.contains(baseline) && !ordered.contains(baseline)) {
    ordered.add(baseline);
  }
  if (ordered.isEmpty) {
    ordered.addAll(available.toList()..sort());
  }
  return ordered.join('+');
}

/// Maps a locale tag to its Tesseract code, or null if unmapped. Accepts full
/// tags (`es_ES`, `zh-Hans-CN`) and uses the primary language subtag.
String? tesseractCodeForLocale(String localeTag) {
  final primary = localeTag.split(RegExp('[_-]')).first.toLowerCase();
  return isoToTesseract[primary];
}
