import { Cache } from "react-native-cache";
import { LIBRETRANSLATE_BASE_URL } from "@/constants/api";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Settings } from "../lib/settings";

type language_t = string;

const cache = new Cache({
  namespace: "translation_terrace",
  policy: {
    maxEntries: 50000, // if unspecified, it can have unlimited entries
    stdTTL: 0, // the standard ttl as number in seconds, default: 0 (unlimited)
  },
  backend: AsyncStorage,
});

export type language_matrix_entry = {
  code: string;
  name: string;
  targets: string[];
};

export type language_matrix = {
  [key: string]: language_matrix_entry;
};

export async function fetchWithTimeout(
  url: string,
  options: RequestInit,
  timeout = 5000
): Promise<Response> {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error("timeout")), timeout)
    ),
  ]);
}

export class LanguageServer {
  constructor(public baseUrl: string) {}

  async fetchLanguages(timeout = 500): Promise<language_matrix> {
    let data = {};
    const res = await fetchWithTimeout(
      this.baseUrl + "/languages",
      {
        headers: {
          "Content-Type": "application/json",
        },
      },
      timeout
    );
    try {
      data = await res.json();
    } catch (e) {
      throw new Error(`Parsing data from ${await res.text()}: ${e}`);
    }
    try {
      return Object.fromEntries(
        Object.values(data as language_matrix_entry[]).map(
          (obj: language_matrix_entry) => {
            return [obj["code"], obj];
          }
        )
      );
    } catch (e) {
      throw new Error(
        `Can't extract values from data: ${JSON.stringify(data)}`
      );
    }
  }

  static async getDefault() {
    const settings = await Settings.getDefault();
    return new LanguageServer(
      (await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL
    );
  }
}

export class Translator {
  constructor(
    public source: language_t,
    public defaultTarget: string = "en",
    private _languageServer: LanguageServer
  ) {}

  get languageServer() {
    return this._languageServer;
  }

  async translate(text: string, target: string | undefined = undefined) {
    const url = this._languageServer.baseUrl + `/translate`;
    console.log(url);
    const postData = {
      method: "POST",
      body: JSON.stringify({
        q: text,
        source: this.source,
        target: target || this.defaultTarget,
        format: "text",
        alternatives: 3,
        api_key: "",
      }),
      headers: { "Content-Type": "application/json" },
    };

    console.debug("Requesting %s with %o", url, postData);

    const res = await fetch(url, postData);

    const data = await res.json();
    if (res.status === 200) {
      console.log(data);
      return data.translatedText;
    } else {
      console.error(data);
    }
  }

  static async getDefault(defaultTarget: string | undefined = undefined) {
    const settings = await Settings.getDefault();
    const source = await settings.getHostLanguage();
    return new Translator(
      source,
      defaultTarget,
      await LanguageServer.getDefault()
    );
  }
}

export class CachedTranslator extends Translator {
  async translate(text: string, target: string | undefined = undefined) {
    const targetKey = target || this.defaultTarget;
    // console.debug(`Translating from ${this.source} -> ${targetKey}`)
    const key1 = `${this.source}::${targetKey}::${text}`;
    const tr1 = await cache.get(key1);
    if (tr1) return tr1;
    const tr2 = await super.translate(text, target);
    const key2 = `${this.source}::${targetKey}::${text}`;
    await cache.set(key2, tr2);
    return tr2;
  }

  static async getDefault(defaultTarget: string | undefined = undefined) {
    const settings = await Settings.getDefault();
    const source = await settings.getHostLanguage() || "en";
    return new CachedTranslator(
      source,
      defaultTarget,
      await LanguageServer.getDefault()
    );
  }
}