import {
  DEFAULT_LOCALE,
  LOCALE_STORAGE_KEY,
  SUPPORTED_LOCALES,
  type Locale,
  type TomlObject,
  type TomlValue,
  type TranslatorInput,
  type TranslatorOptions,
} from "./types";

function mergeTomlObjects(base: TomlObject, override: TomlObject): TomlObject {
  const result: TomlObject = { ...base };

  for (const [key, value] of Object.entries(override)) {
    const currentValue = result[key];
    if (
      typeof currentValue === "object" &&
      currentValue !== null &&
      typeof value === "object" &&
      value !== null
    ) {
      result[key] = mergeTomlObjects(currentValue as TomlObject, value);
      continue;
    }
    result[key] = value;
  }

  return result;
}

function isSupportedLocale(value: string): value is Locale {
  return (SUPPORTED_LOCALES as readonly string[]).includes(value);
}

function parseToml(raw: string): TomlObject {
  const lines = raw.split(/\r?\n/);
  const root: TomlObject = {};
  let currentPath: string[] = [];

  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (!line || line.startsWith("#")) {
      continue;
    }

    if (line.startsWith("[") && line.endsWith("]")) {
      const sectionName = line.slice(1, -1).trim();
      currentPath = sectionName
        ? sectionName
            .split(".")
            .map((part) => part.trim())
            .filter(Boolean)
        : [];
      continue;
    }

    const eqIndex = line.indexOf("=");
    if (eqIndex === -1) {
      continue;
    }

    const key = line.slice(0, eqIndex).trim();
    const valueRaw = line.slice(eqIndex + 1).trim();
    if (!key) {
      continue;
    }

    let value = valueRaw;
    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1);
    }

    let cursor: TomlObject = root;
    for (const section of currentPath) {
      if (!cursor[section] || typeof cursor[section] === "string") {
        cursor[section] = {};
      }
      cursor = cursor[section] as TomlObject;
    }

    cursor[key] = value;
  }

  return root;
}

function getValue(target: TomlObject, key: string): string | undefined {
  const parts = key.split(".");
  let cursor: TomlValue = target;
  for (const part of parts) {
    if (typeof cursor !== "object" || cursor === null) {
      return undefined;
    }
    cursor = (cursor as TomlObject)[part];
    if (cursor === undefined) {
      return undefined;
    }
  }
  return typeof cursor === "string" ? cursor : undefined;
}

function detectLocale(): Locale {
  if (typeof window === "undefined") {
    return DEFAULT_LOCALE;
  }

  const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY);
  if (stored && isSupportedLocale(stored)) {
    return stored;
  }

  const pathLocale = window.location.pathname.split("/")[1];
  if (pathLocale && isSupportedLocale(pathLocale)) {
    return pathLocale;
  }

  const browserLang = window.navigator.language.toLowerCase();
  if (browserLang.startsWith("ko")) {
    return "ko";
  }

  return DEFAULT_LOCALE;
}

function formatTemplate(
  template: string,
  vars?: Record<string, string | number>,
  options?: TranslatorOptions,
): string {
  const normalizedTemplate = options?.normalizeEscapedNewlines
    ? template.replace(/\\n/g, "\n")
    : template;

  if (!vars) {
    return normalizedTemplate;
  }

  return normalizedTemplate.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
    const value = vars[key];
    if (value === undefined || value === null) {
      return match;
    }
    return String(value);
  });
}

export function createTomlTranslator(
  input: TranslatorInput,
  options?: TranslatorOptions,
) {
  const translations: Record<Locale, TomlObject> = {
    ko: input.ko
      .map((raw) => parseToml(raw))
      .reduce<TomlObject>((merged, current) => mergeTomlObjects(merged, current), {}),
    en: input.en
      .map((raw) => parseToml(raw))
      .reduce<TomlObject>((merged, current) => mergeTomlObjects(merged, current), {}),
  };

  return function t(
    key: string,
    fallback?: string,
    vars?: Record<string, string | number>,
  ): string {
    const locale = detectLocale();
    const value = getValue(translations[locale], key);
    if (value && value.length > 0) {
      return formatTemplate(value, vars, options);
    }
    return formatTemplate(fallback ?? key, vars, options);
  };
}
