import type { Messages, ResolvedI18n } from "./index.ts";

export function flattenMessages(
  messages: Messages,
  prefix = "",
  output: Record<string, string> = {},
): Record<string, string> {
  for (const [key, value] of Object.entries(messages)) {
    const path = prefix ? `${prefix}.${key}` : key;
    if (typeof value === "string") output[path] = value;
    else if (value && typeof value === "object" && !Array.isArray(value))
      flattenMessages(value as Messages, path, output);
  }
  return output;
}

export function localeFallbacks(locale: string, fallback = "en"): string[] {
  const normalized = locale.replace(/_/g, "-");
  const values = [normalized];
  const base = normalized.split("-")[0]!;
  if (base !== normalized) values.push(base);
  if (!values.includes(fallback)) values.push(fallback);
  return values;
}

export function translationCoverage(i18n: ResolvedI18n): Record<
  string,
  {
    translated: number;
    total: number;
    percentage: number;
    missing: string[];
    extra: string[];
  }
> {
  const canonical = flattenMessages(i18n.messages[i18n.default] ?? {});
  const canonicalKeys = new Set(Object.keys(canonical));
  const total = canonicalKeys.size;
  const output: Record<
    string,
    { translated: number; total: number; percentage: number; missing: string[]; extra: string[] }
  > = {};
  for (const lang of i18n.langs) {
    const messages = flattenMessages(i18n.messages[lang] ?? {});
    const keys = new Set(Object.keys(messages));
    const missing = [...canonicalKeys].filter((key) => !keys.has(key)).sort();
    const extra = [...keys].filter((key) => !canonicalKeys.has(key)).sort();
    const translated = total - missing.length;
    output[lang] = {
      translated,
      total,
      percentage: total ? Math.round((translated / total) * 10000) / 100 : 100,
      missing,
      extra,
    };
  }
  return output;
}

export interface LocaleFormatter {
  number(value: number, options?: Intl.NumberFormatOptions): string;
  currency(
    value: number,
    currency: string,
    options?: Omit<Intl.NumberFormatOptions, "style" | "currency">,
  ): string;
  date(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
  relative(
    value: number,
    unit: Intl.RelativeTimeFormatUnit,
    options?: Intl.RelativeTimeFormatOptions,
  ): string;
  list(values: string[], options?: Intl.ListFormatOptions): string;
}

export function createLocaleFormatter(
  locale: string,
  timeZone?: string,
  calendar?: string,
): LocaleFormatter {
  const numberCache = new Map<string, Intl.NumberFormat>();
  const dateCache = new Map<string, Intl.DateTimeFormat>();
  const key = (value: unknown) => JSON.stringify(value ?? {});
  return {
    number(value, options) {
      const cacheKey = key(options);
      let format = numberCache.get(cacheKey);
      if (!format) {
        format = new Intl.NumberFormat(locale, options);
        numberCache.set(cacheKey, format);
      }
      return format.format(value);
    },
    currency(value, currency, options) {
      return new Intl.NumberFormat(locale, { ...options, style: "currency", currency }).format(
        value,
      );
    },
    date(value, options = { dateStyle: "medium" }) {
      const resolved = {
        ...options,
        ...(timeZone ? { timeZone } : {}),
        ...(calendar ? { calendar } : {}),
      };
      const cacheKey = key(resolved);
      let format = dateCache.get(cacheKey);
      if (!format) {
        format = new Intl.DateTimeFormat(locale, resolved);
        dateCache.set(cacheKey, format);
      }
      return format.format(value instanceof Date ? value : new Date(value));
    },
    relative: (value, unit, options) =>
      new Intl.RelativeTimeFormat(locale, options).format(value, unit),
    list: (values, options) => new Intl.ListFormat(locale, options).format(values),
  };
}

function complexExpression(template: string, start: number) {
  const header = /^\{(\w+),\s*(plural|select),\s*/.exec(template.slice(start));
  if (!header) return null;
  let cursor = start + header[0].length;
  const choices: Record<string, string> = {};
  while (cursor < template.length) {
    while (/\s/.test(template[cursor] ?? "")) cursor++;
    if (template[cursor] === "}")
      return { name: header[1]!, kind: header[2]!, choices, end: cursor + 1 };
    const key = /^(=?[\w-]+)/.exec(template.slice(cursor));
    if (!key) return null;
    cursor += key[0].length;
    while (/\s/.test(template[cursor] ?? "")) cursor++;
    if (template[cursor] !== "{") return null;
    const bodyStart = ++cursor;
    let depth = 1;
    while (cursor < template.length && depth) {
      if (template[cursor] === "{") depth++;
      else if (template[cursor] === "}") depth--;
      cursor++;
    }
    if (depth) return null;
    choices[key[0]] = template.slice(bodyStart, cursor - 1);
  }
  return null;
}

/** ICU-style plural/select templates with exact values and recursive interpolation. */
export function formatMessage(
  template: string,
  params: Record<string, string | number>,
  locale: string,
): string {
  let result = "";
  for (let cursor = 0; cursor < template.length;) {
    const expression = template[cursor] === "{" ? complexExpression(template, cursor) : null;
    if (!expression) {
      result += template[cursor++]!;
      continue;
    }
    const raw = params[expression.name];
    const selector = expression.kind === "plural" ? `=${Number(raw ?? 0)}` : String(raw ?? "other");
    const category =
      expression.kind === "plural"
        ? new Intl.PluralRules(locale).select(Number(raw ?? 0))
        : selector;
    const selected =
      expression.choices[selector] ??
      expression.choices[category] ??
      expression.choices.other ??
      "";
    result += formatMessage(selected.replace(/#/g, String(raw ?? 0)), params, locale);
    cursor = expression.end;
  }
  result = result.replace(/\{(\w+)\}/g, (_match, name: string) =>
    name in params ? String(params[name]) : `{${name}}`,
  );
  return result;
}
