/**
 * Settings owns the settings modal DOM, model/voice selects, and system
 * message. Fetches models and voices from the API, populates selects,
 * and persists selections to localStorage.
 */

const MODELS_ENDPOINT = '/v1/models';
const MODEL_KEY = 'odidere_model';
const PROVIDER_KEY = 'odidere_provider';
const VOICE_KEY = 'odidere_voice';
const SYSTEM_MESSAGE_KEY = 'odidere_system_message';
const ICONS_URL = '/static/icons.svg';

export class Settings {
  /**
   * @param {Object} options
   * @param {Document} options.document
   * @param {string} options.ttsURL - kokoro-fastapi base URL for voice fetching
   */
  constructor({ document, ttsURL }) {
    this.document = document;
    this.ttsURL = ttsURL;
    this.modelContextSizes = new Map();

    // DOM Elements
    this.$model = document.getElementById('model');
    this.$voice = document.getElementById('voice');
    this.$settings = document.getElementById('settings');
    this.$settingsOverlay = document.getElementById('settings-overlay');
    this.$settingsClose = document.getElementById('settings-close');
    this.$settingsTabs = document.querySelectorAll('.settings-tab');
    this.$settingsPanels = document.querySelectorAll('.settings-tab-panel');
    this.$systemMessageInput = document.getElementById('system-message-input');
    this.$saveSystemMessage = document.getElementById('save-system-message');
  }

  // --- Modal ---

  open() {
    this.$settingsOverlay.classList.add('open');
    this._loadSystemMessage();
    this.switchTab('model-voice');
    this.$settingsClose.focus();
  }

  close() {
    this.$settingsOverlay.classList.remove('open');
    this.$settings.focus();
  }

  switchTab(name) {
    this.$settingsTabs.forEach(($tab) => {
      const isActive = $tab.dataset.tab === name;
      $tab.classList.toggle('active', isActive);
      $tab.setAttribute('aria-selected', String(isActive));
    });
    this.$settingsPanels.forEach(($panel) => {
      const isActive = $panel.dataset.tabPanel === name;
      $panel.classList.toggle('active', isActive);
      $panel.hidden = !isActive;
    });
  }

  // --- Model select ---

  async fetchModels() {
    try {
      const res = await fetch(MODELS_ENDPOINT);
      if (!res.ok) throw new Error(`${res.status}`);
      const data = await res.json();
      this.populateModels(
        data.providers,
        data.default_provider,
        data.default_model,
      );
    } catch (e) {
      console.error('failed to fetch models:', e);
      this._populateModelsFallback();
    }
  }

  populateModels(providers, defaultProvider, defaultModel) {
    this.$model.innerHTML = '';
    this.modelContextSizes.clear();

    const sortedProviders = Object.keys(providers).sort();

    for (const provider of sortedProviders) {
      const $optgroup = this.document.createElement('optgroup');
      $optgroup.label = provider;

      for (const m of providers[provider].sort((a, b) =>
        a.model.localeCompare(b.model),
      )) {
        const $opt = this.document.createElement('option');
        $opt.value = m.model;
        $opt.dataset.provider = provider;
        $opt.dataset.model = m.model;
        if (m.context_size) {
          this.modelContextSizes.set(m.model, m.context_size);
        }

        switch (m.status) {
          case 'available':
            $opt.textContent = m.model;
            break;
          case 'not_found':
            $opt.textContent = `~~${m.model}~~ (not found)`;
            $opt.disabled = true;
            $opt.classList.add('model-unavailable-not-found');
            break;
          case 'provider_error':
            $opt.textContent = `${m.model} (provider error)`;
            $opt.disabled = true;
            $opt.classList.add('model-unavailable-error');
            break;
        }

        $optgroup.appendChild($opt);
      }

      this.$model.appendChild($optgroup);
    }

    this.loadModel(defaultProvider, defaultModel);
  }

  loadModel(defaultProvider, defaultModel) {
    const storedProvider = localStorage.getItem(PROVIDER_KEY);
    const storedModel = localStorage.getItem(MODEL_KEY);

    let $opt;
    if (storedProvider && storedModel) {
      $opt = this.$model.querySelector(
        `option[data-provider="${CSS.escape(storedProvider)}"][data-model="${CSS.escape(storedModel)}"]`,
      );
    }
    if (!$opt && defaultProvider && defaultModel) {
      $opt = this.$model.querySelector(
        `option[data-provider="${CSS.escape(defaultProvider)}"][data-model="${CSS.escape(defaultModel)}"]`,
      );
    }
    if (!$opt && this.$model.options.length > 0) {
      for (const opt of this.$model.options) {
        if (!opt.disabled) {
          $opt = opt;
          break;
        }
      }
    }

    if ($opt) {
      this.$model.value = $opt.value;
    }
  }

  getModel() {
    const $opt = this.$model.options[this.$model.selectedIndex];
    return {
      provider: $opt?.dataset.provider,
      model: $opt?.dataset.model,
    };
  }

  getModelContextSize(model) {
    return this.modelContextSizes.get(model) ?? null;
  }

  // --- Voice select ---

  async fetchVoices() {
    try {
      const res = await fetch(`${this.ttsURL}/v1/audio/voices`);
      if (!res.ok) throw new Error(`${res.status}`);
      const data = await res.json();

      // Filter out legacy v0 voices.
      const filtered = data.voices.filter((v) => {
        const id = typeof v === 'string' ? v : v.id;
        return !id.includes('_v0');
      });
      const voiceIds = filtered.map((v) => (typeof v === 'string' ? v : v.id));
      this.populateVoiceSelect(voiceIds);
    } catch (e) {
      console.error('failed to fetch voices:', e);
      this.$voice.innerHTML = '';

      const $opt = this.document.createElement('option');
      $opt.value = '';
      $opt.disabled = true;
      $opt.selected = true;
      $opt.textContent = 'Failed to load';
      this.$voice.appendChild($opt);
    }
  }

  populateVoiceSelect(voices) {
    const langLabels = {
      af: '🇺🇸 American English (F)',
      am: '🇺🇸 American English (M)',
      bf: '🇬🇧 British English (F)',
      bm: '🇬🇧 British English (M)',
      jf: '🇯🇵 Japanese (F)',
      jm: '🇯🇵 Japanese (M)',
      zf: '🇨🇳 Mandarin Chinese (F)',
      zm: '🇨🇳 Mandarin Chinese (M)',
      ef: '🇪🇸 Spanish (F)',
      em: '🇪🇸 Spanish (M)',
      ff: '🇫🇷 French (F)',
      fm: '🇫🇷 French (M)',
      hf: '🇮🇳 Hindi (F)',
      hm: '🇮🇳 Hindi (M)',
      if: '🇮🇹 Italian (F)',
      im: '🇮🇹 Italian (M)',
      pf: '🇧🇷 Brazilian Portuguese (F)',
      pm: '🇧🇷 Brazilian Portuguese (M)',
      kf: '🇰🇷 Korean (F)',
      km: '🇰🇷 Korean (M)',
    };

    // Group voices by their two-character prefix.
    const groups = {};
    for (const voice of voices) {
      const prefix = voice.substring(0, 2);
      if (!groups[prefix]) groups[prefix] = [];
      groups[prefix].push(voice);
    }

    this.$voice.innerHTML = '';

    // Add "Auto" option.
    const $auto = this.document.createElement('option');
    $auto.value = '';
    $auto.textContent = '🌐 Auto';
    this.$voice.appendChild($auto);

    const sortedPrefixes = Object.keys(groups).sort((a, b) => {
      if (a[0] !== b[0]) return a[0].localeCompare(b[0]);
      return a[1].localeCompare(b[1]);
    });

    for (const prefix of sortedPrefixes) {
      const label = langLabels[prefix] || prefix.toUpperCase();

      const $optgroup = this.document.createElement('optgroup');
      $optgroup.label = label;

      for (const voice of groups[prefix].sort()) {
        const name = voice.substring(3).replace(/_/g, ' ');
        const displayName = name.charAt(0).toUpperCase() + name.slice(1);

        const $opt = this.document.createElement('option');
        $opt.value = voice;
        $opt.textContent = displayName;
        $optgroup.appendChild($opt);
      }

      this.$voice.appendChild($optgroup);
    }

    this.loadVoice();
  }

  loadVoice() {
    const stored = localStorage.getItem(VOICE_KEY);

    if (stored === null) {
      this.$voice.value = '';
      return;
    }

    const escaped = stored === '' ? '' : CSS.escape(stored);
    const exists =
      stored === '' || this.$voice.querySelector(`option[value="${escaped}"]`);

    if (!exists) {
      this.$voice.value = '';
      return;
    }

    this.$voice.value = stored;
  }

  getVoice() {
    return this.$voice.value;
  }

  // --- System message ---

  loadSystemMessage() {
    const stored = localStorage.getItem(SYSTEM_MESSAGE_KEY);
    this.$systemMessageInput.value = stored || '';
  }

  saveSystemMessage() {
    const value = this.$systemMessageInput.value;
    localStorage.setItem(SYSTEM_MESSAGE_KEY, value);

    // Brief visual feedback on the save button.
    this.$saveSystemMessage.replaceChildren(this._icon('check'));
    this.$saveSystemMessage.classList.add('settings-save-btn--success');

    setTimeout(() => {
      this.$saveSystemMessage.textContent = 'Save';
      this.$saveSystemMessage.classList.remove('settings-save-btn--success');
    }, 1500);
  }

  // --- Model/voice change persistence ---

  saveModelSelection() {
    const $opt = this.$model.options[this.$model.selectedIndex];
    localStorage.setItem(PROVIDER_KEY, $opt.dataset.provider);
    localStorage.setItem(MODEL_KEY, $opt.dataset.model);
  }

  saveVoiceSelection() {
    localStorage.setItem(VOICE_KEY, this.$voice.value);
  }

  // --- Private ---

  _populateModelsFallback() {
    this.$model.innerHTML = '';

    const $opt = this.document.createElement('option');
    $opt.value = '';
    $opt.disabled = true;
    $opt.selected = true;
    $opt.textContent = 'Failed to load';
    this.$model.appendChild($opt);
  }

  _icon(name) {
    const svg = this.document.createElementNS(
      'http://www.w3.org/2000/svg',
      'svg',
    );
    svg.setAttribute('class', 'icon');

    const use = this.document.createElementNS(
      'http://www.w3.org/2000/svg',
      'use',
    );
    use.setAttribute('href', `${ICONS_URL}#${name}`);

    svg.appendChild(use);
    return svg;
  }
}
