import { LanguageCode } from 'iso-639-1';
import { z } from 'zod';

import { DittoTranslator } from '@/interfaces/DittoTranslator.ts';
import { languageSchema } from '@/schema.ts';

interface DeepLTranslatorOpts {
  /** DeepL base URL to use. Default: 'https://api.deepl.com' */
  baseUrl?: string;
  /** DeepL API key. */
  apiKey: string;
  /** Custom fetch implementation. */
  fetch?: typeof fetch;
}

export class DeepLTranslator implements DittoTranslator {
  private readonly baseUrl: string;
  private readonly apiKey: string;
  private readonly fetch: typeof fetch;

  readonly provider = 'DeepL.com';

  constructor(opts: DeepLTranslatorOpts) {
    this.baseUrl = opts.baseUrl ?? 'https://api.deepl.com';
    this.fetch = opts.fetch ?? globalThis.fetch;
    this.apiKey = opts.apiKey;
  }

  async translate(
    texts: string[],
    source: LanguageCode | undefined,
    dest: LanguageCode,
    opts?: { signal?: AbortSignal },
  ) {
    const { translations } = await this.translateMany(texts, source, dest, opts);

    return {
      results: translations.map((value) => value.text),
      source_lang: translations[0]?.detected_source_language as LanguageCode,
    };
  }

  /** DeepL translate request. */
  private async translateMany(
    texts: string[],
    source: LanguageCode | undefined,
    targetLanguage: LanguageCode,
    opts?: { signal?: AbortSignal },
  ) {
    const body: any = {
      text: texts,
      target_lang: targetLanguage.toUpperCase(),
      tag_handling: 'html',
      split_sentences: '1',
    };
    if (source) {
      body.source_lang = source.toUpperCase();
    }

    const url = new URL('/v2/translate', this.baseUrl);

    const request = new Request(url, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: {
        'Authorization': `DeepL-Auth-Key ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      signal: opts?.signal,
    });

    const response = await this.fetch(request);
    const json = await response.json();

    if (!response.ok) {
      throw new Error(json['message']);
    }

    return DeepLTranslator.schema().parse(json);
  }

  /** DeepL response schema.
   * https://developers.deepl.com/docs/api-reference/translate/openapi-spec-for-text-translation */
  private static schema() {
    return z.object({
      translations: z.array(
        z.object({
          detected_source_language: languageSchema,
          text: z.string(),
        }),
      ),
    });
  }
}
