import {
  BaseTranslation,
  i18nObject,
  LocaleTranslationFunctions,
  typesafeI18nObject,
} from "npm:typesafe-i18n@5.18.1";

// #region Copy bunch of types from source since package does not export them
// deno-lint-ignore no-explicit-any
export type Arguments = any[];

// This is an unique symbol so it will cause type clashes with original package.
declare const localized: unique symbol;
export type LocalizedString = string & {
  readonly [localized]: unknown;
};
// deno-lint-ignore no-explicit-any
export type FormatterFunction<T = any, U = unknown> = (value: T) => U;
export type BaseFormatters = {
  [formatter: string]: FormatterFunction;
};
export type BaseTranslationFunction = (...args: Arguments) => LocalizedString;
export type TranslationFunctions<
  T extends
    | BaseTranslation
    | BaseTranslation[]
    | Readonly<BaseTranslation>
    | Readonly<BaseTranslation[]> = BaseTranslation
> = {
  [key in keyof T]: T[key] extends string
    ? BaseTranslationFunction
    : // deno-lint-ignore no-explicit-any
    T[key] extends Record<any, any>
    ? TranslationFunctions<T[key]>
    : never;
};
//#endregion

export type typesafeI18nObjectReturn<
  L extends string,
  T extends BaseTranslation
> = ReturnType<typeof typesafeI18nObject<L, T>>;

// Reimplement i18n function to allow reloading
export const i18n = <
  L extends string,
  T extends
    | BaseTranslation
    | BaseTranslation[]
    | Readonly<BaseTranslation>
    | Readonly<BaseTranslation[]>,
  TF extends TranslationFunctions<T>,
  F extends BaseFormatters
>(
  translations: Record<L, T>,
  formatters: Record<L, F>
): {
  // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
  translate: LocaleTranslationFunctions<L, T, TF>;
  /** Debugging */
  // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
  cache: LocaleTranslationFunctions<L, T, TF>;
  reloadLanguage: (
    language: L,
    translations:
      | BaseTranslation
      | BaseTranslation[]
      | Readonly<BaseTranslation>
      | Readonly<BaseTranslation[]>
  ) => void;
  reloadLanguages: (
    languages: Record<
      string,
      | BaseTranslation
      | BaseTranslation[]
      | Readonly<BaseTranslation>
      | Readonly<BaseTranslation[]>
    >
  ) => void;
} => {
  // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
  const cache = {} as LocaleTranslationFunctions<L, T, TF>;

  // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
  const i18nTool = new Proxy<LocaleTranslationFunctions<L, T, TF>>(
    // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
    {} as LocaleTranslationFunctions<L, T, TF>,
    {
      get: (_target, locale: L): TF | null =>
        cache[locale] ||
        // @ts-ignore this error is fine, it is because different Symbol in LocalizedFunction
        (cache[locale] = i18nObject<L, T, TF, F>(
          locale,
          translations[locale],
          formatters[locale]
        )),
    }
  );

  return {
    translate: i18nTool,
    cache: cache,
    reloadLanguage: (language, translations) => {
      cache[language] = i18nObject(language, translations);
    },
    reloadLanguages: (languages) => {
      for (const [key, value] of Object.entries(languages)) {
        // @ts-ignore this is fine
        cache[key] = i18nObject(key, value);
      }
    },
  };
};

export function init<S extends Parameters<typeof i18n>[0], L extends keyof S>(
  // deno-lint-ignore no-unused-vars
  languages: S,
  // deno-lint-ignore no-unused-vars
  defaultLanguageName: L,
  // deno-lint-ignore no-explicit-any
  languagesData: Record<string, any>,
  // deno-lint-ignore no-explicit-any
  formatters?: Record<string, any>
) {
  const i18nx = i18n(languagesData, formatters ?? {});

  const translate = i18nx.translate as Record<
    keyof S,
    // @ts-ignore should be fine
    typesafeI18nObjectReturn<L, S[L]>
  >;

  const reloadAll: (
    // deno-lint-ignore no-explicit-any
    languages: Record<string, any>
  ) => void = i18nx.reloadLanguages;

  // @ts-ignore should be fine
  const reloadOne: (
    language: L,
    translations:
      | BaseTranslation
      | BaseTranslation[]
      | Readonly<BaseTranslation>
      | Readonly<BaseTranslation[]>
  ) => void = i18nx.reloadLanguage;

  return [translate, reloadAll, i18nx.cache, reloadOne] as [
    typeof translate,
    typeof reloadAll,
    typeof i18nx.cache,
    typeof reloadOne
  ];
}

export default init;
