import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useState, type ReactElement, type ReactNode } from "react";
import type { PlatformTranslationDictionary, PlatformTranslations } from "../types";
import { generatedTranslations } from "./generatedTranslations";

export type PlatformLanguage = {
  code: string;
  label: string;
  nativeLabel?: string;
};

export type PlatformLanguageContextValue = {
  availableLanguages: PlatformLanguage[];
  enabledLanguages: PlatformLanguage[];
  selectableLanguages: PlatformLanguage[];
  language: string;
  languageLabel: string;
  setLanguage: (code: string) => void;
  t: (key: string, fallback?: string) => string;
  translateText: (value: string) => string;
};

type PlatformLanguageProviderProps = {
  children: ReactNode;
  systemAvailableLanguages?: PlatformLanguage[] | null;
  systemEnabledLanguageCodes?: string[] | null;
  userEnabledLanguageCodes?: string[] | null;
  defaultLanguage?: string | null;
  preferredLanguageCode?: string | null;
  onLanguageChange?: (code: string) => void;
  moduleTranslations?: Array<PlatformTranslations | null | undefined>;
};

const STORAGE_KEY = "govoplan.language";
const PRIMARY_LANGUAGE = "en";
const I18N_MESSAGE_SEPARATOR = "::";
const TRANSLATABLE_ATTRIBUTES = ["aria-label", "alt", "placeholder", "title"];
const SKIP_TRANSLATION_SELECTOR = "script, style, code, pre, textarea, [data-i18n-skip]";
const textNodeOriginals = new WeakMap<Text, string>();

export const DEFAULT_AVAILABLE_LANGUAGES: PlatformLanguage[] = [
{ code: "en", label: "i18n:govoplan-core.english.649df08a", nativeLabel: "i18n:govoplan-core.language_native_english" },
{ code: "de", label: "i18n:govoplan-core.german.da91388c", nativeLabel: "i18n:govoplan-core.language_native_german" }];


export const DEFAULT_TRANSLATIONS: PlatformTranslations = {
  en: generatedTranslations.en,
  de: generatedTranslations.de
};

const PlatformLanguageContext = createContext<PlatformLanguageContextValue | null>(null);

export function PlatformLanguageProvider({
  children,
  systemAvailableLanguages,
  systemEnabledLanguageCodes,
  userEnabledLanguageCodes,
  defaultLanguage,
  preferredLanguageCode,
  onLanguageChange,
  moduleTranslations = []
}: PlatformLanguageProviderProps) {
  const availableLanguages = useMemo(() => normalizeLanguages(systemAvailableLanguages), [systemAvailableLanguages]);
  const enabledLanguages = useMemo(
    () => languagesForCodes(availableLanguages, systemEnabledLanguageCodes),
    [availableLanguages, systemEnabledLanguageCodes]
  );
  const selectableLanguages = useMemo(() => {
    const selected = userEnabledLanguageCodes?.length ? languagesForCodes(enabledLanguages, userEnabledLanguageCodes) : enabledLanguages;
    return selected.length ? selected : enabledLanguages;
  }, [enabledLanguages, userEnabledLanguageCodes]);
  const fallbackLanguage = preferredLanguage(selectableLanguages, defaultLanguage);
  const [language, setLanguageState] = useState(() => preferredLanguage(selectableLanguages, preferredLanguageCode ?? storedLanguage() ?? defaultLanguage));

  useEffect(() => {
    if (selectableLanguages.some((item) => item.code === language)) return;
    setLanguageState(fallbackLanguage);
  }, [fallbackLanguage, language, selectableLanguages]);

  useEffect(() => {
    if (!preferredLanguageCode) return;
    const preferred = preferredLanguage(selectableLanguages, preferredLanguageCode);
    if (preferred) setLanguageState((current) => current === preferred ? current : preferred);
  }, [preferredLanguageCode, selectableLanguages]);

  useEffect(() => {
    document.documentElement.lang = language;
    localStorage.setItem(STORAGE_KEY, language);
  }, [language]);

  const translations = useMemo(() => mergeTranslations(DEFAULT_TRANSLATIONS, moduleTranslations), [moduleTranslations]);
  const dictionary = useMemo(
    () => ({ ...(translations[PRIMARY_LANGUAGE] ?? {}), ...(translations[language] ?? {}) }),
    [language, translations]
  );
  const translateText = useCallback((value: string) => translatePlainText(value, language, dictionary), [dictionary, language]);
  const t = useCallback((key: string, fallback = key) => {
    if (normalizeLanguageCode(language) === PRIMARY_LANGUAGE) return fallback;
    return dictionary[key] ?? translatePlainText(fallback, language, dictionary);
  }, [dictionary, language]);

  useEffect(() => {
    return installDomTranslationBridge(language, dictionary);
  }, [dictionary, language]);

  function setLanguage(code: string) {
    const normalized = normalizeLanguageCode(code);
    if (!selectableLanguages.some((item) => item.code === normalized)) return;
    setLanguageState(normalized);
    onLanguageChange?.(normalized);
  }

  const languageLabel = selectableLanguages.find((item) => item.code === language)?.label ?? language.toUpperCase();
  const contextValue = useMemo(
    () => ({ availableLanguages, enabledLanguages, selectableLanguages, language, languageLabel, setLanguage, t, translateText }),
    [availableLanguages, enabledLanguages, language, languageLabel, selectableLanguages, t, translateText]
  );

  return (
    <PlatformLanguageContext.Provider value={contextValue}>
      {children}
    </PlatformLanguageContext.Provider>);

}

export function usePlatformLanguage(): PlatformLanguageContextValue {
  const value = useContext(PlatformLanguageContext);
  if (value) return value;
  const language = preferredLanguage(DEFAULT_AVAILABLE_LANGUAGES, null);
  return {
    availableLanguages: DEFAULT_AVAILABLE_LANGUAGES,
    enabledLanguages: DEFAULT_AVAILABLE_LANGUAGES,
    selectableLanguages: DEFAULT_AVAILABLE_LANGUAGES,
    language,
    languageLabel: DEFAULT_AVAILABLE_LANGUAGES.find((item) => item.code === language)?.label ?? language.toUpperCase(),
    setLanguage: () => undefined,
    t: (_key: string, fallback?: string) => fallback ?? _key,
    translateText: (value: string) => value
  };
}

export function normalizeLanguageCode(value?: string | null): string {
  const normalized = String(value || "").trim().toLowerCase().replace(/_/g, "-");
  const parts = normalized.split(/[^a-z0-9]+/).filter(Boolean);
  return parts.join("-") || "en";
}

export function i18nMessage(key: string, values?: Record<string, unknown>): string {
  if (!values || Object.keys(values).length === 0) return key;
  return `${key}${I18N_MESSAGE_SEPARATOR}${encodeURIComponent(JSON.stringify(values))}`;
}

export function translateReactNode(node: ReactNode, translateText: (value: string) => string): ReactNode {
  if (typeof node === "string") return translateText(node);
  if (node === null || node === undefined || typeof node === "boolean") return node;
  if (Array.isArray(node)) return Children.map(node, (child) => translateReactNode(child, translateText));
  if (isValidElement(node)) {
    const props = node.props as { children?: ReactNode };
    if (props.children === undefined) return node;
    return cloneElement(
      node as ReactElement<{ children?: ReactNode }>,
      undefined,
      translateReactNode(props.children, translateText)
    );
  }
  return node;
}

function normalizeLanguages(languages?: PlatformLanguage[] | null): PlatformLanguage[] {
  const source = languages?.length ? languages : DEFAULT_AVAILABLE_LANGUAGES;
  const seen = new Set<string>();
  return source.flatMap((language) => {
    const code = normalizeLanguageCode(language.code);
    if (seen.has(code)) return [];
    seen.add(code);
    return [{ ...language, code }];
  });
}

function languagesForCodes(languages: PlatformLanguage[], codes?: string[] | null): PlatformLanguage[] {
  if (!codes?.length) return languages;
  const enabled = new Set(codes.map(normalizeLanguageCode));
  const enabledPrimary = new Set(Array.from(enabled).map(primaryLanguageCode));
  const filtered = languages.filter((language) => enabled.has(language.code) || enabledPrimary.has(primaryLanguageCode(language.code)));
  return filtered.length ? filtered : languages;
}

function preferredLanguage(languages: PlatformLanguage[], preferred?: string | null): string {
  const codes = new Set(languages.map((item) => item.code));
  const requested = normalizeLanguageCode(preferred ?? storedLanguage() ?? browserLanguage());
  if (codes.has(requested)) return requested;
  const primary = primaryLanguageCode(requested);
  const primaryMatch = languages.find((item) => primaryLanguageCode(item.code) === primary);
  if (primaryMatch) return primaryMatch.code;
  if (codes.has(PRIMARY_LANGUAGE)) return PRIMARY_LANGUAGE;
  return languages[0]?.code ?? PRIMARY_LANGUAGE;
}

function primaryLanguageCode(value: string): string {
  return normalizeLanguageCode(value).split("-", 1)[0];
}

function storedLanguage(): string | null {
  if (typeof localStorage === "undefined") return null;
  return localStorage.getItem(STORAGE_KEY);
}

function browserLanguage(): string | null {
  if (typeof navigator === "undefined") return null;
  return navigator.language;
}

function mergeTranslations(base: PlatformTranslations, catalogs: Array<PlatformTranslations | null | undefined>): PlatformTranslations {
  const merged: PlatformTranslations = {};
  for (const source of [base, ...catalogs]) {
    if (!source) continue;
    for (const [language, dictionary] of Object.entries(source)) {
      const code = normalizeLanguageCode(language);
      merged[code] = { ...(merged[code] ?? {}), ...dictionary };
    }
  }
  return merged;
}

function translatePlainText(value: string, language: string, dictionary: PlatformTranslationDictionary): string {
  const match = value.match(/^(\s*)(.*?)(\s*)$/s);
  if (!match) return value;
  const [, prefix, body, suffix] = match;
  if (!body) return value;
  if (body.startsWith("i18n:")) {
    const { key, values } = parseI18nMessage(body);
    if (Object.prototype.hasOwnProperty.call(dictionary, key)) {
      const translated = formatI18nMessage(dictionary[key] ?? key, values, dictionary);
      return `${prefix}${translated}${suffix}`;
    }
  }
  if (body.includes("i18n:")) {
    return `${prefix}${translateEmbeddedI18nMessages(body, dictionary)}${suffix}`;
  }
  if (normalizeLanguageCode(language) === PRIMARY_LANGUAGE) return value;
  const translated = dictionary[body] ?? body;
  return `${prefix}${translated}${suffix}`;
}

function translateEmbeddedI18nMessages(value: string, dictionary: PlatformTranslationDictionary): string {
  let output = "";
  let index = 0;
  const keys = Object.keys(dictionary).filter((key) => key.startsWith("i18n:")).sort((left, right) => right.length - left.length);

  while (index < value.length) {
    const tokenStart = value.indexOf("i18n:", index);
    if (tokenStart < 0) {
      output += value.slice(index);
      break;
    }
    output += value.slice(index, tokenStart);
    const key = keys.find((candidate) => value.startsWith(candidate, tokenStart));
    if (!key) {
      output += "i18n:";
      index = tokenStart + "i18n:".length;
      continue;
    }

    let token = key;
    let nextIndex = tokenStart + key.length;
    if (value.startsWith(I18N_MESSAGE_SEPARATOR, nextIndex)) {
      const encodedStart = nextIndex + I18N_MESSAGE_SEPARATOR.length;
      let encodedEnd = encodedStart;
      while (encodedEnd < value.length && !/[\s<>"'`]/.test(value[encodedEnd])) encodedEnd += 1;
      token = value.slice(tokenStart, encodedEnd);
      nextIndex = encodedEnd;
    }
    const { values } = parseI18nMessage(token);
    output += formatI18nMessage(dictionary[key] ?? key, values, dictionary);
    index = nextIndex;
  }

  return output;
}

function parseI18nMessage(value: string): { key: string; values: Record<string, unknown> } {
  const [key, encodedValues] = value.split(I18N_MESSAGE_SEPARATOR, 2);
  if (!encodedValues) return { key, values: {} };
  try {
    const parsed = JSON.parse(decodeURIComponent(encodedValues));
    return { key, values: parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {} };
  } catch {
    return { key, values: {} };
  }
}

function formatI18nMessage(template: string, values: Record<string, unknown>, dictionary: PlatformTranslationDictionary): string {
  return template.replace(/\{([A-Za-z0-9_]+)\}/g, (_match, name) => {
    const value = values[name];
    if (value === null || value === undefined) return "";
    if (typeof value === "string" && value.startsWith("i18n:")) {
      const nested = parseI18nMessage(value);
      return formatI18nMessage(dictionary[nested.key] ?? nested.key, nested.values, dictionary);
    }
    return String(value);
  });
}

function installDomTranslationBridge(language: string, dictionary: PlatformTranslationDictionary): () => void {
  if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => undefined;
  const root = document.body ?? document.getElementById("root");
  if (!root) return () => undefined;
  let frame: number | null = null;

  function run() {
    frame = null;
    translateDomSubtree(root, language, dictionary);
  }

  function schedule() {
    if (frame !== null) return;
    frame = window.requestAnimationFrame(run);
  }

  schedule();

  const observer = new MutationObserver(schedule);
  observer.observe(root, {
    attributes: true,
    attributeFilter: TRANSLATABLE_ATTRIBUTES,
    characterData: true,
    childList: true,
    subtree: true
  });

  return () => {
    observer.disconnect();
    if (frame !== null) window.cancelAnimationFrame(frame);
    translateDomSubtree(root, PRIMARY_LANGUAGE, dictionary);
  };
}

function translateDomSubtree(root: Element, language: string, dictionary: PlatformTranslationDictionary): void {
  translateElementAttributes(root, language, dictionary);

  const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
  let current = walker.nextNode();
  while (current) {
    if (current.nodeType === Node.TEXT_NODE) {
      translateTextNode(current as Text, language, dictionary);
    } else if (current.nodeType === Node.ELEMENT_NODE) {
      translateElementAttributes(current as Element, language, dictionary);
    }
    current = walker.nextNode();
  }
}

function translateTextNode(node: Text, language: string, dictionary: PlatformTranslationDictionary): void {
  const parent = node.parentElement;
  if (!parent || parent.closest(SKIP_TRANSLATION_SELECTOR)) return;
  const current = node.nodeValue ?? "";
  if (!current.trim()) return;

  const stored = textNodeOriginals.get(node);
  const knownTranslation = stored ? translatePlainText(stored, language, dictionary) : null;
  const original = stored && (current === stored || current === knownTranslation) ? stored : current;
  textNodeOriginals.set(node, original);

  const next = translatePlainText(original, language, dictionary);
  if (current !== next) node.nodeValue = next;
}

function translateElementAttributes(element: Element, language: string, dictionary: PlatformTranslationDictionary): void {
  if (element.closest(SKIP_TRANSLATION_SELECTOR)) return;
  for (const attribute of TRANSLATABLE_ATTRIBUTES) {
    const current = element.getAttribute(attribute);
    if (!current?.trim()) continue;

    const storageAttribute = `data-i18n-original-${attribute}`;
    const stored = element.getAttribute(storageAttribute);
    const knownTranslation = stored ? translatePlainText(stored, language, dictionary) : null;
    const original = stored && (current === stored || current === knownTranslation) ? stored : current;
    element.setAttribute(storageAttribute, original);

    const next = translatePlainText(original, language, dictionary);
    if (current !== next) element.setAttribute(attribute, next);
  }
}
