/**
 * Kokoro-FastAPI TTS Provider
 *
 * Default-tier TTS provider backed by Kokoro-FastAPI.
 * CPU-based, always available, Apache 2.0 license.
 *
 * Features:
 * - 53 built-in voices across 8 languages
 * - Speed control: 0.25x to 4.0x
 * - Output formats: mp3, wav, opus, flac
 * - Voice metadata derived from ID prefix (language, gender, accent)
 *
 * Voice ID format: {prefix}_{name}
 *   - First character: language/accent code (a=American, b=British, etc.)
 *   - Second character: gender code (f=Female, m=Male)
 *
 * Issue #393
 */

import { BaseTTSProvider } from "./base-tts.provider";
import type { SpeechTier, VoiceInfo, AudioFormat } from "../interfaces/speech-types";

// ==========================================
// Constants
// ==========================================

/** Audio formats supported by Kokoro-FastAPI */
export const KOKORO_SUPPORTED_FORMATS: readonly AudioFormat[] = [
  "mp3",
  "wav",
  "opus",
  "flac",
] as const;

/** Speed range supported by Kokoro-FastAPI */
export const KOKORO_SPEED_RANGE = {
  min: 0.25,
  max: 4.0,
} as const;

/** Default voice for Kokoro */
const KOKORO_DEFAULT_VOICE = "af_heart";

/** Default audio format for Kokoro */
const KOKORO_DEFAULT_FORMAT: AudioFormat = "mp3";

// ==========================================
// Voice prefix mapping
// ==========================================

/**
 * Mapping of voice ID prefix (first two characters) to language/accent/gender metadata.
 *
 * Kokoro voice IDs follow the pattern: {lang}{gender}_{name}
 * - lang: a=American, b=British, e=Spanish, f=French, h=Hindi, j=Japanese, p=Portuguese, z=Chinese
 * - gender: f=Female, m=Male
 */
const VOICE_PREFIX_MAP: Record<string, { language: string; gender: string; accent: string }> = {
  af: { language: "en-US", gender: "female", accent: "American" },
  am: { language: "en-US", gender: "male", accent: "American" },
  bf: { language: "en-GB", gender: "female", accent: "British" },
  bm: { language: "en-GB", gender: "male", accent: "British" },
  ef: { language: "es", gender: "female", accent: "Spanish" },
  em: { language: "es", gender: "male", accent: "Spanish" },
  ff: { language: "fr", gender: "female", accent: "French" },
  fm: { language: "fr", gender: "male", accent: "French" },
  hf: { language: "hi", gender: "female", accent: "Hindi" },
  hm: { language: "hi", gender: "male", accent: "Hindi" },
  jf: { language: "ja", gender: "female", accent: "Japanese" },
  jm: { language: "ja", gender: "male", accent: "Japanese" },
  pf: { language: "pt-BR", gender: "female", accent: "Portuguese" },
  pm: { language: "pt-BR", gender: "male", accent: "Portuguese" },
  zf: { language: "zh", gender: "female", accent: "Chinese" },
  zm: { language: "zh", gender: "male", accent: "Chinese" },
};

// ==========================================
// Voice catalog
// ==========================================

/** Raw voice catalog entry */
interface KokoroVoiceEntry {
  /** Voice ID (e.g. "af_heart") */
  id: string;
  /** Human-readable label (e.g. "Heart") */
  label: string;
}

/**
 * Complete catalog of Kokoro built-in voices.
 *
 * Organized by language/accent prefix:
 * - af_: American English Female
 * - am_: American English Male
 * - bf_: British English Female
 * - bm_: British English Male
 * - ef_: Spanish Female
 * - em_: Spanish Male
 * - ff_: French Female
 * - hf_: Hindi Female
 * - jf_: Japanese Female
 * - jm_: Japanese Male
 * - pf_: Portuguese Female
 * - zf_: Chinese Female
 * - zm_: Chinese Male
 */
export const KOKORO_VOICES: readonly KokoroVoiceEntry[] = [
  // American English Female (af_)
  { id: "af_heart", label: "Heart" },
  { id: "af_alloy", label: "Alloy" },
  { id: "af_aoede", label: "Aoede" },
  { id: "af_bella", label: "Bella" },
  { id: "af_jessica", label: "Jessica" },
  { id: "af_kore", label: "Kore" },
  { id: "af_nicole", label: "Nicole" },
  { id: "af_nova", label: "Nova" },
  { id: "af_river", label: "River" },
  { id: "af_sarah", label: "Sarah" },
  { id: "af_sky", label: "Sky" },
  // American English Male (am_)
  { id: "am_adam", label: "Adam" },
  { id: "am_echo", label: "Echo" },
  { id: "am_eric", label: "Eric" },
  { id: "am_fenrir", label: "Fenrir" },
  { id: "am_liam", label: "Liam" },
  { id: "am_michael", label: "Michael" },
  { id: "am_onyx", label: "Onyx" },
  { id: "am_puck", label: "Puck" },
  { id: "am_santa", label: "Santa" },
  // British English Female (bf_)
  { id: "bf_alice", label: "Alice" },
  { id: "bf_emma", label: "Emma" },
  { id: "bf_isabella", label: "Isabella" },
  { id: "bf_lily", label: "Lily" },
  // British English Male (bm_)
  { id: "bm_daniel", label: "Daniel" },
  { id: "bm_fable", label: "Fable" },
  { id: "bm_george", label: "George" },
  { id: "bm_lewis", label: "Lewis" },
  { id: "bm_oscar", label: "Oscar" },
  // Spanish Female (ef_)
  { id: "ef_dora", label: "Dora" },
  { id: "ef_elena", label: "Elena" },
  { id: "ef_maria", label: "Maria" },
  // Spanish Male (em_)
  { id: "em_alex", label: "Alex" },
  { id: "em_carlos", label: "Carlos" },
  { id: "em_santa", label: "Santa" },
  // French Female (ff_)
  { id: "ff_camille", label: "Camille" },
  { id: "ff_siwis", label: "Siwis" },
  // Hindi Female (hf_)
  { id: "hf_alpha", label: "Alpha" },
  { id: "hf_beta", label: "Beta" },
  // Japanese Female (jf_)
  { id: "jf_alpha", label: "Alpha" },
  { id: "jf_gongitsune", label: "Gongitsune" },
  { id: "jf_nezumi", label: "Nezumi" },
  { id: "jf_tebukuro", label: "Tebukuro" },
  // Japanese Male (jm_)
  { id: "jm_kumo", label: "Kumo" },
  // Portuguese Female (pf_)
  { id: "pf_dora", label: "Dora" },
  // Chinese Female (zf_)
  { id: "zf_xiaobei", label: "Xiaobei" },
  { id: "zf_xiaoni", label: "Xiaoni" },
  { id: "zf_xiaoxiao", label: "Xiaoxiao" },
  { id: "zf_xiaoyi", label: "Xiaoyi" },
  // Chinese Male (zm_)
  { id: "zm_yunjian", label: "Yunjian" },
  { id: "zm_yunxi", label: "Yunxi" },
  { id: "zm_yunxia", label: "Yunxia" },
  { id: "zm_yunyang", label: "Yunyang" },
] as const;

// ==========================================
// Prefix parser
// ==========================================

/** Parsed voice prefix metadata */
export interface VoicePrefixMetadata {
  /** BCP 47 language code (e.g. "en-US", "en-GB", "ja") */
  language: string;
  /** Gender: "female", "male", or "unknown" */
  gender: string;
  /** Human-readable accent label (e.g. "American", "British") */
  accent: string;
}

/**
 * Parse a Kokoro voice ID to extract language, gender, and accent metadata.
 *
 * Voice IDs follow the pattern: {lang}{gender}_{name}
 * The first two characters encode language/accent and gender.
 *
 * @param voiceId - Kokoro voice ID (e.g. "af_heart")
 * @returns Parsed metadata with language, gender, and accent
 */
export function parseVoicePrefix(voiceId: string): VoicePrefixMetadata {
  const prefix = voiceId.substring(0, 2);
  const mapping = VOICE_PREFIX_MAP[prefix];

  if (mapping) {
    return {
      language: mapping.language,
      gender: mapping.gender,
      accent: mapping.accent,
    };
  }

  return {
    language: "unknown",
    gender: "unknown",
    accent: "Unknown",
  };
}

// ==========================================
// Provider class
// ==========================================

/**
 * Kokoro-FastAPI TTS provider (default tier).
 *
 * CPU-based text-to-speech engine with 53 built-in voices across 8 languages.
 * Uses the OpenAI-compatible API exposed by Kokoro-FastAPI.
 *
 * @example
 * ```typescript
 * const kokoro = new KokoroTtsProvider("http://kokoro-tts:8880/v1");
 * const voices = await kokoro.listVoices();
 * const result = await kokoro.synthesize("Hello!", { voice: "af_heart" });
 * ```
 */
export class KokoroTtsProvider extends BaseTTSProvider {
  readonly name = "kokoro";
  readonly tier: SpeechTier = "default";

  /**
   * Create a new Kokoro TTS provider.
   *
   * @param baseURL - Base URL for the Kokoro-FastAPI endpoint (e.g. "http://kokoro-tts:8880/v1")
   * @param defaultVoice - Default voice ID (defaults to "af_heart")
   * @param defaultFormat - Default audio format (defaults to "mp3")
   */
  constructor(
    baseURL: string,
    defaultVoice: string = KOKORO_DEFAULT_VOICE,
    defaultFormat: AudioFormat = KOKORO_DEFAULT_FORMAT
  ) {
    super(baseURL, defaultVoice, defaultFormat);
  }

  /**
   * List all available Kokoro voices with metadata.
   *
   * Returns the full catalog of 53 built-in voices with language, gender,
   * and accent information derived from voice ID prefixes.
   *
   * @returns Array of VoiceInfo objects for all Kokoro voices
   */
  override listVoices(): Promise<VoiceInfo[]> {
    const voices: VoiceInfo[] = KOKORO_VOICES.map((entry) => {
      const metadata = parseVoicePrefix(entry.id);
      const genderLabel = metadata.gender === "female" ? "Female" : "Male";

      return {
        id: entry.id,
        name: `${entry.label} (${metadata.accent} ${genderLabel})`,
        language: metadata.language,
        tier: this.tier,
        isDefault: entry.id === this.defaultVoice,
      };
    });

    return Promise.resolve(voices);
  }
}
