// ── Benchmark Voice Picker ───────────────────────────────────────────────
// Searchable benchmark voice selector with avatars plus duration/speed stats.
(function () {
  'use strict';

  const pickers = {};

  function esc(value) {
    return String(value == null ? '' : value)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function norm(value) {
    if (typeof value === 'string') return { id: value, label: value, meta: null, raw: value };
    const id = value?.id || value?.voice || value?.name || value?.voice_id || '';
    const hasExplicitMeta = Object.prototype.hasOwnProperty.call(value || {}, 'meta');
    const looksLikeVoice = !!(value?.display_name || value?.duration || value?.benchmark || value?.has_picture || value?.transcript);
    return {
      id,
      label: value?.label || value?.display_name || value?.name || value?.voice || id,
      meta: hasExplicitMeta ? value.meta : (looksLikeVoice ? value : null),
      raw: value,
    };
  }

  function libraryVoice(id) {
    return (window._voices || []).find(v => v && v.id === id) || null;
  }

  function metaFor(item) {
    return item?.meta || libraryVoice(item?.id) || item?.raw || null;
  }

  function langColor(lang, id) {
    const str = String(lang || id || '').toLowerCase();
    if (str.startsWith('de')) return '#3b82f6';
    if (str.startsWith('en')) return '#10b981';
    if (str.startsWith('fr')) return '#8b5cf6';
    if (str.startsWith('es')) return '#f59e0b';
    if (str.startsWith('it')) return '#ef4444';
    if (str.startsWith('zh')) return '#ec4899';
    if (str.startsWith('ja')) return '#f97316';
    const palette = ['#3b82f6', '#10b981', '#8b5cf6', '#f59e0b', '#ef4444', '#ec4899', '#06b6d4', '#84cc16'];
    let h = 0;
    for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) >>> 0;
    return palette[h % palette.length];
  }

  function avatarHtml(item, size) {
    const id = item?.id || '';
    const meta = metaFor(item);
    const s = `${size}px`;
    const r = `${Math.round(size / 2)}px`;
    if (meta?.has_picture) {
      return `<img src="/api/voice/picture/${encodeURIComponent(id)}" class="bvp-avatar" style="width:${s};height:${s};border-radius:${r};object-fit:cover" alt="">`;
    }
    const icon = window.voiceAvatarIcon ? window.voiceAvatarIcon(meta?.avatar, size) : null;
    if (icon) return icon.replace(/vp-avatar/g, 'bvp-avatar');
    const color = langColor(meta?.lang || meta?.language, id);
    const init = (item?.label || id || '?').trim()[0]?.toUpperCase() || '?';
    return `<span class="bvp-avatar" style="width:${s};height:${s};border-radius:${r};background:${color};color:#fff;font-weight:700;font-size:${Math.round(size * 0.44)}px">${esc(init)}</span>`;
  }

  function numeric(value) {
    const n = Number(value);
    return Number.isFinite(n) ? n : null;
  }

  function statsFor(item) {
    const meta = metaFor(item) || {};
    const b = meta.benchmark || item?.benchmark || {};
    const parts = [];
    const duration = numeric(meta.duration ?? item?.duration);
    const speed = numeric(b.speed ?? b.avg_speed ?? meta.speed);
    const rtf = numeric(b.rtf ?? b.avg_rtf ?? meta.rtf);
    const elapsed = numeric(b.elapsed_sec ?? b.avg_elapsed_sec);
    if (duration != null && duration > 0) parts.push(`${duration.toFixed(1)}s`);
    if (speed != null && speed > 0) parts.push(`${speed.toFixed(2)}x`);
    else if (rtf != null && rtf > 0) parts.push(`RTF ${rtf.toFixed(2)}`);
    if (!parts.length && elapsed != null && elapsed > 0) parts.push(`${elapsed.toFixed(1)}s TTS`);
    if (!parts.length && b.ok === false) parts.push('benchmark failed');
    return parts.join(' · ');
  }

  function searchText(item) {
    const meta = metaFor(item) || {};
    return [
      item?.id,
      item?.label,
      meta.display_name,
      meta.name,
      meta.lang,
      meta.language,
      Array.isArray(meta.tags) ? meta.tags.join(' ') : meta.tags,
      statsFor(item),
    ].filter(Boolean).join(' ').toLowerCase();
  }

  function optionLabel(item) {
    const stats = statsFor(item);
    return `${item.label || item.id}${stats ? ` (${stats})` : ''}`;
  }

  function renderTrigger(picker) {
    const value = picker.select.value;
    const item = picker.items.find(v => v.id === value);
    const content = picker.trigger.querySelector('.bvp-trigger-content');
    if (!item) {
      content.innerHTML = `<span class="bvp-placeholder">${esc(picker.placeholder)}</span>`;
      return;
    }
    const stats = statsFor(item);
    content.innerHTML = `${avatarHtml(item, 24)}<span class="bvp-trigger-main"><span class="bvp-trigger-name">${esc(item.label || item.id)}</span>${stats ? `<span class="bvp-trigger-stats">${esc(stats)}</span>` : ''}</span>`;
  }

  function renderList(picker, filter) {
    const q = String(filter || '').trim().toLowerCase();
    const items = q ? picker.items.filter(item => searchText(item).includes(q)) : picker.items;
    if (!items.length) {
      picker.list.innerHTML = `<div class="bvp-empty">${esc(q ? 'No voices found' : picker.empty)}</div>`;
      return;
    }
    picker.list.innerHTML = items.map(item => {
      const meta = metaFor(item) || {};
      const stats = statsFor(item);
      const sub = [meta.lang || meta.language, meta.file_type].filter(Boolean).join(' · ');
      return `<button class="bvp-item${item.id === picker.select.value ? ' selected' : ''}" type="button" data-value="${esc(item.id)}">
        ${avatarHtml(item, 28)}
        <span class="bvp-item-main"><span class="bvp-item-name">${esc(item.label || item.id)}</span>${sub ? `<span class="bvp-item-sub">${esc(sub)}</span>` : ''}</span>
        ${stats ? `<span class="bvp-item-stats">${esc(stats)}</span>` : ''}
      </button>`;
    }).join('');
  }

  function close(picker) {
    if (!picker) return;
    picker.drop.hidden = true;
    picker.drop.style.display = 'none';
    picker.trigger.classList.remove('open');
  }

  function closeAll(except) {
    Object.values(pickers).forEach(p => { if (p !== except) close(p); });
  }

  function open(picker) {
    closeAll(picker);
    picker.drop.hidden = false;
    picker.drop.style.display = '';
    picker.trigger.classList.add('open');
    picker.search.value = '';
    renderList(picker, '');
    picker.search.focus();
    requestAnimationFrame(() => picker.list.querySelector('.bvp-item.selected')?.scrollIntoView({ block: 'nearest' }));
  }

  function setValue(picker, value, dispatch) {
    picker.select.value = value || '';
    renderTrigger(picker);
    renderList(picker, picker.search.value);
    close(picker);
    if (dispatch !== false) picker.select.dispatchEvent(new Event('change', { bubbles: true }));
    picker.trigger.focus();
  }

  function upgrade(selectId, options) {
    const select = document.getElementById(selectId);
    if (!select) return null;
    if (pickers[selectId]) return pickers[selectId];
    const picker = {
      select,
      items: [],
      placeholder: options?.placeholder || Array.from(select.options).find(o => !o.value)?.textContent || '— select after fetch —',
      empty: options?.empty || 'No voices',
    };
    select.dataset.bvpUpgraded = '1';
    select.style.display = 'none';

    const root = document.createElement('div');
    root.className = 'bvp-root';
    root.dataset.bvpFor = selectId;
    select.parentNode.insertBefore(root, select);

    root.innerHTML = `<button class="bvp-trigger" type="button">
      <span class="bvp-trigger-content"><span class="bvp-placeholder">${esc(picker.placeholder)}</span></span>
      <span class="mdi mdi-chevron-down bvp-chevron"></span>
    </button>
    <div class="bvp-drop" hidden>
      <div class="bvp-search-wrap"><span class="mdi mdi-magnify bvp-search-icon"></span><input class="bvp-search" type="search" placeholder="Search voices..." autocomplete="off" spellcheck="false"></div>
      <div class="bvp-list"></div>
    </div>`;

    Object.assign(picker, {
      root,
      trigger: root.querySelector('.bvp-trigger'),
      drop: root.querySelector('.bvp-drop'),
      search: root.querySelector('.bvp-search'),
      list: root.querySelector('.bvp-list'),
    });

    picker.trigger.addEventListener('click', e => {
      e.preventDefault();
      picker.drop.hidden ? open(picker) : close(picker);
    });
    picker.trigger.addEventListener('keydown', e => {
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(picker); }
      if (e.key === 'ArrowDown') { e.preventDefault(); open(picker); picker.list.querySelector('.bvp-item')?.focus(); }
    });
    picker.search.addEventListener('input', () => renderList(picker, picker.search.value));
    picker.search.addEventListener('keydown', e => {
      if (e.key === 'Escape') { e.preventDefault(); close(picker); picker.trigger.focus(); }
      if (e.key === 'ArrowDown') { e.preventDefault(); picker.list.querySelector('.bvp-item')?.focus(); }
    });
    picker.list.addEventListener('click', e => {
      const item = e.target.closest('.bvp-item');
      if (item) setValue(picker, item.dataset.value, true);
    });
    picker.list.addEventListener('keydown', e => {
      const item = e.target.closest('.bvp-item');
      if (!item) return;
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setValue(picker, item.dataset.value, true); }
      if (e.key === 'ArrowDown') { e.preventDefault(); (item.nextElementSibling || item).focus(); }
      if (e.key === 'ArrowUp') { e.preventDefault(); (item.previousElementSibling || picker.search).focus(); }
      if (e.key === 'Escape') { e.preventDefault(); close(picker); picker.trigger.focus(); }
    });
    select.addEventListener('change', () => { renderTrigger(picker); close(picker); });
    document.addEventListener('click', e => { if (!root.contains(e.target)) close(picker); }, true);

    pickers[selectId] = picker;
    syncFromSelect(selectId);
    return picker;
  }

  function populate(selectId, values, options) {
    const picker = upgrade(selectId, options || {});
    if (!picker) return;
    picker.placeholder = options?.placeholder || picker.placeholder;
    picker.empty = options?.empty || picker.empty;
    const previous = picker.select.value;
    picker.items = (values || []).map(norm).filter(item => item.id);
    picker.select.innerHTML = `<option value="">${esc(picker.placeholder)}</option>` + picker.items.map(item => `<option value="${esc(item.id)}">${esc(optionLabel(item))}</option>`).join('');
    if (previous && picker.items.some(item => item.id === previous)) picker.select.value = previous;
    renderTrigger(picker);
    renderList(picker, picker.search?.value || '');
  }

  function syncFromSelect(selectId) {
    const picker = pickers[selectId];
    if (!picker) return;
    picker.items = Array.from(picker.select.options).filter(o => o.value).map(o => ({ id: o.value, label: o.textContent || o.value, meta: libraryVoice(o.value) || {} }));
    renderTrigger(picker);
    renderList(picker, '');
  }

  function getValue(selectId) {
    return document.getElementById(selectId)?.value || '';
  }

  function set(selectId, value) {
    const picker = pickers[selectId] || upgrade(selectId);
    if (!picker) return;
    setValue(picker, value, true);
  }

  window.BenchmarkVoicePicker = { upgrade, populate, syncFromSelect, getValue, set };
})();
