/**
 * Subtitle auto-selection. Jellyfin returns subtitle streams with a
 * mix of language tags - some have a 3-letter ISO code ('eng'), some
 * a 2-letter code ('en'), some include a region ('en-US'), and some
 * encode the language only in the track name ('English (SRT)'). This
 * module normalises across all of those so a user pref of "eng" still
 * picks up tracks tagged 'English' or 'en-US'.
 */

interface SubStream {
  Index?: number
  Language?: string | null
  Title?: string | null
  DisplayTitle?: string | null
  Codec?: string | null
  IsDefault?: boolean | null
  IsForced?: boolean | null
  IsExternal?: boolean | null
  IsHearingImpaired?: boolean | null
}

const TWO_TO_THREE: Record<string, string> = {
  en: 'eng', es: 'spa', fr: 'fra', de: 'ger', ja: 'jpn', ko: 'kor',
  zh: 'chi', it: 'ita', pt: 'por', ru: 'rus', nl: 'nld', sv: 'swe',
  no: 'nor', da: 'dan', fi: 'fin', pl: 'pol', tr: 'tur', ar: 'ara',
  he: 'heb', hi: 'hin', cs: 'cze', hu: 'hun', ro: 'rum', bg: 'bul',
  uk: 'ukr', el: 'gre', th: 'tha', vi: 'vie', id: 'ind', ms: 'may',
  ca: 'cat', hr: 'hrv', sk: 'slo', sl: 'slv', sr: 'srp', et: 'est',
  lv: 'lav', lt: 'lit', fa: 'per', ta: 'tam', te: 'tel', bn: 'ben',
}

const NAME_TO_CODE: Record<string, string> = {
  english: 'eng', spanish: 'spa', french: 'fra', german: 'ger', japanese: 'jpn',
  korean: 'kor', chinese: 'chi', mandarin: 'chi', cantonese: 'chi', italian: 'ita',
  portuguese: 'por', russian: 'rus', dutch: 'nld', swedish: 'swe', norwegian: 'nor',
  danish: 'dan', finnish: 'fin', polish: 'pol', turkish: 'tur', arabic: 'ara',
  hebrew: 'heb', hindi: 'hin', czech: 'cze', hungarian: 'hun', romanian: 'rum',
  bulgarian: 'bul', ukrainian: 'ukr', greek: 'gre', thai: 'tha', vietnamese: 'vie',
  indonesian: 'ind', malay: 'may', catalan: 'cat', croatian: 'hrv', slovak: 'slo',
  slovenian: 'slv', serbian: 'srp', estonian: 'est', latvian: 'lav', lithuanian: 'lit',
  persian: 'per', farsi: 'per', tamil: 'tam', telugu: 'tel', bengali: 'ben',
}

/**
 * Normalise an arbitrary language string to a 3-letter ISO 639-2 code.
 * Handles 2-letter codes ('en'), region-tagged codes ('en-US', 'pt_BR'),
 * full language names ('English'), and the alternate 3-letter codes
 * Jellyfin sometimes returns ('en' tagged as eng).
 */
export function normalizeLang(s: string | null | undefined): string {
  if (!s) return ''
  const lower = s.toLowerCase().trim()
  if (NAME_TO_CODE[lower]) return NAME_TO_CODE[lower]
  const base = lower.split(/[-_]/)[0]
  if (TWO_TO_THREE[base]) return TWO_TO_THREE[base]
  // Already a 3-letter code or unknown - return as-is so an exact match
  // still works for less common languages.
  return base
}

function isTextCodec(codec: string | null | undefined): boolean {
  const c = (codec || '').toLowerCase()
  return c === 'srt' || c === 'subrip' || c === 'ass' || c === 'ssa' || c === 'vtt' || c === 'webvtt' || c === 'mov_text'
}

function trackMatchesLanguage(track: SubStream, wantNorm: string, wantRaw: string): boolean {
  if (track.Language) {
    if (normalizeLang(track.Language) === wantNorm) return true
  }
  // Sometimes the language only lives in the track title:
  //   "English (SRT)"
  //   "Eng"
  //   "Forced English"
  const haystack = `${track.Title || ''} ${track.DisplayTitle || ''}`.toLowerCase()
  if (!haystack.trim()) return false
  if (haystack.includes(wantRaw.toLowerCase())) return true
  // Look for any name that normalises to the same code.
  for (const [name, code] of Object.entries(NAME_TO_CODE)) {
    if (code === wantNorm && haystack.includes(name)) return true
  }
  return false
}

/**
 * Pick the best subtitle stream for the user's preferred language.
 *
 * Ranking, in order:
 *   1. Full-language non-forced text-based track (the typical pick)
 *   2. Full-language non-forced image-based track (DVDSUB / PGS)
 *   3. Full-language forced text track
 *   4. Full-language forced image track
 *   5. Default-flagged track of any language (only if 1-4 missed)
 *
 * Forced + hearing-impaired tracks are ranked lower because they
 * usually translate only foreign dialogue or include audio cues, which
 * isn't what someone setting "always English" actually wants.
 */
export function pickSubtitle(streams: SubStream[], preferredLanguage: string): SubStream | null {
  if (streams.length === 0) return null
  const wantNorm = normalizeLang(preferredLanguage)
  const wantRaw = preferredLanguage.trim()

  const matching = streams.filter(s => trackMatchesLanguage(s, wantNorm, wantRaw))

  function score(s: SubStream): number {
    let n = 0
    if (!s.IsForced) n += 8
    if (isTextCodec(s.Codec)) n += 4
    if (!s.IsHearingImpaired) n += 2
    if (s.IsDefault) n += 1
    return n
  }

  if (matching.length > 0) {
    return [...matching].sort((a, b) => score(b) - score(a))[0]
  }

  // No language match - fall back to the server-marked default rather
  // than a random first track. Useful for users whose preferred
  // language doesn't exist on a given file (so we don't force-select
  // some unrelated track).
  return streams.find(s => s.IsDefault) || null
}
