// ui_kits/website/Search.jsx
//
// Site-wide search overlay. Triggered by the magnifying-loupe button in
// Header.jsx. Indexes everything that's already in memory:
//
//   • Blog posts          → window.SAMPLE_POSTS
//   • Podcast episodes    → window.BUZZSPROUT_EPISODES || window.PODCAST_EPISODES
//   • YouTube videos      → window.SAMPLE_VIDEOS
//   • Section landing pgs → NAV_ITEMS (Salud, Belleza, …, Tienda, Podcasts)
//
// No backend. No fetch. Pure client-side fuzzy includes() against
// title + excerpt + category, scored loosely so title hits sort first.
//
// Click a result → fires window 'app:navigate' with view+section, or
// calls back to App via onOpenPost / external URL for podcasts & videos.

const searchStyles = {
  // Backdrop covers the whole viewport, click-outside dismisses.
  backdrop: {
    position: 'fixed', inset: 0, zIndex: 200,
    background: 'rgba(20,20,20,0.45)',
    backdropFilter: 'blur(6px)',
    WebkitBackdropFilter: 'blur(6px)',
    display: 'flex', justifyContent: 'center', alignItems: 'flex-start',
    padding: '64px 24px 24px',
    animation: 'tnt-search-fade 180ms var(--ease-out)',
  },
  panel: {
    width: '100%', maxWidth: 720,
    background: '#fff',
    borderRadius: 14,
    border: '1px solid var(--color-divider)',
    boxShadow: '0 30px 80px rgba(15,15,15,0.28)',
    overflow: 'hidden',
    display: 'flex', flexDirection: 'column',
    maxHeight: 'calc(100vh - 96px)',
    animation: 'tnt-search-rise 220ms var(--ease-out)',
  },
  inputRow: {
    display: 'flex', alignItems: 'center', gap: 14,
    padding: '18px 22px',
    borderBottom: '1px solid var(--color-divider)',
    background: '#fff',
  },
  inputIcon: { color: 'var(--brand-leaf)', flexShrink: 0 },
  input: {
    flex: 1,
    border: 'none', outline: 'none', background: 'transparent',
    fontFamily: 'var(--font-serif)',
    fontSize: 22, fontWeight: 400, letterSpacing: '-0.005em',
    color: '#151414',
    padding: '6px 0',
  },
  kbd: {
    fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 600,
    letterSpacing: '0.06em', textTransform: 'uppercase',
    padding: '4px 8px', borderRadius: 6,
    border: '1px solid var(--color-divider)',
    color: '#525150',
    background: '#FBF8F2',
  },

  // Scroll region for results
  body: {
    overflowY: 'auto',
    padding: '8px 0 14px',
    background: '#fff',
  },

  // Section heading (Articles / Podcast / …)
  groupHead: {
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    padding: '14px 22px 6px',
    fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700,
    letterSpacing: '0.16em', textTransform: 'uppercase',
    color: 'var(--color-fg-subtle)',
  },
  groupCount: { color: '#9a9893', fontWeight: 500, letterSpacing: '0.08em' },

  // Single result row
  row: {
    display: 'flex', alignItems: 'flex-start', gap: 14,
    padding: '12px 22px',
    cursor: 'pointer',
    border: 'none', background: 'transparent',
    width: '100%', textAlign: 'left',
    transition: 'background 140ms var(--ease-out)',
  },
  rowActive: { background: 'var(--brand-cream)' },
  rowIcon: {
    width: 36, height: 36, borderRadius: 8,
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    flexShrink: 0,
    fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700,
    letterSpacing: '0.04em',
  },
  rowMain: { flex: 1, minWidth: 0 },
  rowTitle: {
    fontFamily: 'var(--font-serif)', fontSize: 16, fontWeight: 600,
    color: '#151414', lineHeight: 1.3,
    overflow: 'hidden', textOverflow: 'ellipsis',
    display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical',
  },
  rowMeta: {
    fontFamily: 'var(--font-sans)', fontSize: 12, color: '#7a7a78',
    marginTop: 3,
    display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap',
    whiteSpace: 'nowrap',
  },
  rowDot: { width: 3, height: 3, borderRadius: '50%', background: '#cfcdc8' },
  rowArrow: {
    color: '#9a9893',
    flexShrink: 0, alignSelf: 'center',
    transition: 'transform 160ms var(--ease-out), color 160ms var(--ease-out)',
  },

  // Empty + zero-state
  empty: {
    padding: '36px 22px 28px',
    textAlign: 'center',
    color: '#7a7a78',
    fontFamily: 'var(--font-sans)', fontSize: 14, lineHeight: 1.6,
  },
  suggestHead: {
    padding: '20px 22px 8px',
    fontFamily: 'var(--font-sans)', fontSize: 11, fontWeight: 700,
    letterSpacing: '0.16em', textTransform: 'uppercase',
    color: 'var(--color-fg-subtle)',
  },
  suggestChips: {
    display: 'flex', flexWrap: 'wrap', gap: 8,
    padding: '0 22px 14px',
  },
  chip: {
    fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 500,
    letterSpacing: '0.04em',
    padding: '8px 14px', borderRadius: 9999,
    background: 'var(--brand-cream)', color: '#383838',
    border: '1px solid var(--color-divider)',
    cursor: 'pointer',
    transition: 'all 160ms var(--ease-out)',
  },

  // Footer hints
  foot: {
    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
    padding: '10px 18px',
    borderTop: '1px solid var(--color-divider)',
    background: '#FBF8F2',
    fontFamily: 'var(--font-sans)', fontSize: 11.5,
    color: '#7a7a78', letterSpacing: '0.04em',
  },
  footHints: { display: 'flex', alignItems: 'center', gap: 14 },
  footHint: { display: 'flex', alignItems: 'center', gap: 6 },
  footKbd: {
    fontFamily: 'var(--font-sans)', fontSize: 10.5, fontWeight: 700,
    letterSpacing: '0.04em', color: '#525150',
    padding: '2px 6px', borderRadius: 4,
    background: '#fff', border: '1px solid var(--color-divider)',
    minWidth: 18, textAlign: 'center',
  },
};

// Inject keyframes once.
function injectSearchAnims() {
  if (document.getElementById('tnt-search-anims')) return;
  const s = document.createElement('style');
  s.id = 'tnt-search-anims';
  s.textContent = `
    @keyframes tnt-search-fade { from { opacity: 0 } to { opacity: 1 } }
    @keyframes tnt-search-rise {
      from { opacity: 0; transform: translateY(-12px) scale(0.985) }
      to   { opacity: 1; transform: translateY(0)     scale(1) }
    }
  `;
  document.head.appendChild(s);
}

// ---- Index building ------------------------------------------------------

// Lowercase a string with diacritics stripped, so "sinusitis" matches
// "Sinusítis" and "frambuesa" matches "Frambuesas".
function norm(s) {
  if (!s) return '';
  return String(s).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}

// Loose includes-based scorer. Higher score = better match.
function scoreHit(item, q) {
  if (!q) return 0;
  const t = norm(item.title);
  const e = norm(item.excerpt);
  const c = norm(item.category);
  let score = 0;
  if (t.startsWith(q))           score += 100;
  if (t.includes(q))             score += 60;
  if (c.includes(q))             score += 35;
  if (e.includes(q))             score += 20;
  // Word-boundary boost
  const re = new RegExp(`\\b${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
  if (re.test(t))                score += 25;
  return score;
}

// Build a flat index from window.* sources. Re-built whenever the
// overlay opens so freshly-loaded YouTube / Buzzsprout / blog feeds
// are picked up.
function buildIndex(lang, tr) {
  const out = [];

  // Sections (Salud, Belleza, …)
  const nav = window.NAV_ITEMS || [];
  nav.forEach(n => {
    const label = tr ? tr(n.i18n) : n.key;
    out.push({
      kind: 'section',
      key: 's-' + n.key,
      title: label,
      excerpt: n.key === 'Shop'
        ? (lang === 'es' ? 'Productos y libros recomendados.' : 'Recommended products and books.')
        : n.key === 'Podcasts'
          ? (lang === 'es' ? 'Todos los episodios del podcast.' : 'All podcast episodes.')
          : (lang === 'es' ? `Todos los artículos de ${label}.` : `All articles in ${label}.`),
      category: n.key,
      nav: n.key,
    });
  });

  // Articles
  const resolve = window.resolvePost;
  const posts = window.SAMPLE_POSTS || [];
  posts.forEach(p => {
    const r = resolve ? resolve(p, lang) : p;
    out.push({
      kind: 'article',
      key: 'a-' + (r.id || r.slug || r.title),
      title: r.title,
      excerpt: r.excerpt,
      category: r.category,
      categoryLabel: r.categoryLabel || r.category,
      date: r.date,
      readMinutes: r.readMinutes,
      _raw: p,
    });
  });

  // Podcast episodes
  const eps = (window.BUZZSPROUT_EPISODES && window.BUZZSPROUT_EPISODES.length)
    ? window.BUZZSPROUT_EPISODES
    : (window.PODCAST_EPISODES || []);
  eps.forEach(ep => {
    out.push({
      kind: 'podcast',
      key: 'p-' + (ep.appleId || ep.id || ep.n || ep.title),
      title: ep.title,
      excerpt: ep.excerpt,
      category: ep.topic,
      n: ep.n,
      minutes: ep.minutes,
      date: ep.dateLong || ep.date,
      _raw: ep,
    });
  });

  // YouTube videos
  const vids = window.SAMPLE_VIDEOS || [];
  vids.forEach(v => {
    out.push({
      kind: 'video',
      key: 'v-' + (v.id || v.title),
      title: typeof v.title === 'object' ? (v.title[lang] || v.title.es || v.title.en) : v.title,
      excerpt: typeof v.excerpt === 'object' ? (v.excerpt[lang] || v.excerpt.es || v.excerpt.en) : (v.excerpt || ''),
      category: v.category,
      duration: v.duration,
      videoId: v.id,
      _raw: v,
    });
  });

  return out;
}

// ---- Visual classifier per result type -----------------------------------
// Tiny colored badge in the row icon slot, so kinds are scannable.
const KIND_BADGE = {
  article: { bg: '#EAF1ED', fg: '#2E6F5A', glyph: 'A' },
  podcast: { bg: '#F2EAF6', fg: '#6E3D8C', glyph: 'P' },
  video:   { bg: '#FDEAEA', fg: '#B83A3A', glyph: 'V' },
  section: { bg: '#FBF2E0', fg: '#9A6B12', glyph: '§' },
};

function ResultIcon({ kind }) {
  const b = KIND_BADGE[kind] || KIND_BADGE.article;
  // Use a real glyph (not just letter) for video / podcast — feels more designed.
  if (kind === 'video' && window.PlayIcon) {
    return (
      <span style={{ ...searchStyles.rowIcon, background: b.bg, color: b.fg }}>
        <PlayIcon size={16}/>
      </span>
    );
  }
  if (kind === 'podcast' && window.Headphones) {
    return (
      <span style={{ ...searchStyles.rowIcon, background: b.bg, color: b.fg }}>
        <Headphones size={16}/>
      </span>
    );
  }
  if (kind === 'section' && window.LeafMark) {
    return (
      <span style={{ ...searchStyles.rowIcon, background: b.bg, color: b.fg }}>
        <LeafMark size={18}/>
      </span>
    );
  }
  return (
    <span style={{ ...searchStyles.rowIcon, background: b.bg, color: b.fg }}>
      {b.glyph}
    </span>
  );
}

// ---- Result row ----------------------------------------------------------

function ResultRow({ hit, active, onHover, onPick }) {
  const tr = useT();
  const kindLabel = {
    article: tr ? tr('search.kind.article') : 'Article',
    podcast: tr ? tr('search.kind.podcast') : 'Podcast',
    video:   tr ? tr('search.kind.video')   : 'Video',
    section: tr ? tr('search.kind.section') : 'Section',
  }[hit.kind];

  const meta = [];
  meta.push(kindLabel);
  if (hit.kind === 'article' && hit.categoryLabel) meta.push(hit.categoryLabel);
  if (hit.kind === 'article' && hit.readMinutes)   meta.push(`${hit.readMinutes} min`);
  if (hit.kind === 'podcast' && hit.n)            meta.push(`#${hit.n}`);
  if (hit.kind === 'podcast' && hit.minutes)      meta.push(`${hit.minutes}:00`);
  if (hit.kind === 'video' && hit.duration)       meta.push(hit.duration);
  if (hit.kind === 'video' && hit.category)       meta.push(hit.category);

  return (
    <button
      type="button"
      style={{ ...searchStyles.row, ...(active ? searchStyles.rowActive : {}) }}
      onMouseEnter={onHover}
      onClick={onPick}>
      <ResultIcon kind={hit.kind}/>
      <span style={searchStyles.rowMain}>
        <span style={searchStyles.rowTitle}>{hit.title}</span>
        <span style={searchStyles.rowMeta}>
          {meta.map((m, i) => (
            <React.Fragment key={i}>
              {i > 0 && <span style={searchStyles.rowDot}/>}
              <span>{m}</span>
            </React.Fragment>
          ))}
        </span>
      </span>
      <span style={{
        ...searchStyles.rowArrow,
        color: active ? 'var(--brand-leaf)' : '#9a9893',
        transform: active ? 'translateX(3px)' : 'translateX(0)',
      }}>
        {window.ChevronRight ? <ChevronRight size={18}/> : '→'}
      </span>
    </button>
  );
}

// ---- The overlay itself --------------------------------------------------

function SearchOverlay({ open, onClose, lang }) {
  const tr = useT();
  const [q, setQ] = React.useState('');
  const [active, setActive] = React.useState(0);
  const inputRef = React.useRef(null);
  const bodyRef = React.useRef(null);

  React.useEffect(() => { injectSearchAnims(); }, []);

  // Focus the input + reset state on open
  React.useEffect(() => {
    if (!open) return;
    setQ('');
    setActive(0);
    // Defer so the element is mounted
    requestAnimationFrame(() => { inputRef.current && inputRef.current.focus(); });
  }, [open]);

  // Esc closes
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, onClose]);

  // Lock body scroll while open
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, [open]);

  // Build the index once per open + once per language change.
  const index = React.useMemo(() => {
    if (!open) return [];
    return buildIndex(lang, tr);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, lang]);

  // Compute hits (top 24, score-sorted), grouped by kind.
  const { hits, grouped } = React.useMemo(() => {
    const nq = norm(q.trim());
    if (!nq) return { hits: [], grouped: {} };
    const scored = [];
    for (const item of index) {
      const s = scoreHit(item, nq);
      if (s > 0) scored.push({ ...item, _score: s });
    }
    scored.sort((a, b) => b._score - a._score);
    const top = scored.slice(0, 24);
    const g = { article: [], podcast: [], video: [], section: [] };
    top.forEach(h => { (g[h.kind] || (g[h.kind] = [])).push(h); });
    return { hits: top, grouped: g };
  }, [q, index]);

  // Keep active index in range
  React.useEffect(() => {
    if (active >= hits.length) setActive(0);
  }, [hits.length, active]);

  // ↑/↓ navigate, Enter picks
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => {
      if (!hits.length) return;
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setActive(i => Math.min(i + 1, hits.length - 1));
      } else if (e.key === 'ArrowUp') {
        e.preventDefault();
        setActive(i => Math.max(i - 1, 0));
      } else if (e.key === 'Enter') {
        e.preventDefault();
        const hit = hits[active];
        if (hit) pick(hit);
      }
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, hits, active]);

  function pick(hit) {
    onClose();
    if (hit.kind === 'article') {
      // Navigate to the article's own URL; App resolves the slug back to the post.
      const raw = (window.SAMPLE_POSTS || []).find(p => (p.id || p.slug || p.title) === (hit._raw.id || hit._raw.slug || hit._raw.title)) || hit._raw;
      navigate({ view: 'post', postSlug: slugForPost(raw) });
    } else if (hit.kind === 'podcast') {
      // Start playback (the audio singleton is shared with MiniPlayer +
      // PodcastPage), then deep-link to the episode — PodcastPage scrolls it
      // into view and reflects playback.
      const ep = hit._raw;
      if (typeof window.playPodcastEpisode === 'function') window.playPodcastEpisode(ep);
      navigate({ view: 'podcast', podcastSlug: episodeSlug(ep) });
    } else if (hit.kind === 'video') {
      // Open the actual YouTube video if we have an ID; otherwise the channel.
      if (hit.videoId) {
        window.open(`https://www.youtube.com/watch?v=${hit.videoId}`, '_blank', 'noopener');
      } else if (window.CHANNEL_URL) {
        window.open(window.CHANNEL_URL, '_blank', 'noopener');
      }
    } else if (hit.kind === 'section') {
      navigate(navSpecForKey(hit.nav));
    }
  }

  // Suggested topics shown when input is empty.
  const SUGGESTIONS = lang === 'es'
    ? ['Sinusitis', 'Magnesio', 'Felicidad', 'Sueño', 'Frambuesas', 'Vitamina D', 'Belleza', 'Caminar']
    : ['Sinusitis', 'Magnesium', 'Happiness', 'Sleep', 'Raspberries', 'Vitamin D', 'Beauty', 'Walking'];

  if (!open) return null;

  // Group display order: articles first (most numerous), then podcast, video, section.
  const GROUP_ORDER = ['article', 'podcast', 'video', 'section'];
  const groupLabel = {
    article: tr('search.group.articles'),
    podcast: tr('search.group.podcast'),
    video:   tr('search.group.videos'),
    section: tr('search.group.sections'),
  };

  // Map flat 'hits' index → which group + position so hover highlight syncs.
  // (We render rows in group order, so we re-walk in the same order.)
  let flatIdx = 0;

  return (
    <div
      style={searchStyles.backdrop}
      onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div style={searchStyles.panel} role="dialog" aria-label={tr('search.dialog.label')}>
        <div style={searchStyles.inputRow}>
          <span style={searchStyles.inputIcon}>
            {window.SearchIcon ? <SearchIcon size={22}/> : '🔍'}
          </span>
          <input
            ref={inputRef}
            type="text"
            value={q}
            onChange={(e) => { setQ(e.target.value); setActive(0); }}
            placeholder={tr('search.placeholder')}
            style={searchStyles.input}
            autoComplete="off"
            spellCheck="false"
          />
          <span style={searchStyles.kbd}>esc</span>
        </div>

        <div style={searchStyles.body} ref={bodyRef}>
          {q.trim() === '' && (
            <React.Fragment>
              <div style={searchStyles.suggestHead}>{tr('search.suggest.head')}</div>
              <div style={searchStyles.suggestChips}>
                {SUGGESTIONS.map(s => (
                  <button key={s} type="button" style={searchStyles.chip}
                    onMouseEnter={(e) => { e.currentTarget.style.background = '#fff'; e.currentTarget.style.borderColor = 'var(--brand-leaf)'; e.currentTarget.style.color = 'var(--brand-leaf)'; }}
                    onMouseLeave={(e) => { e.currentTarget.style.background = 'var(--brand-cream)'; e.currentTarget.style.borderColor = 'var(--color-divider)'; e.currentTarget.style.color = '#383838'; }}
                    onClick={() => { setQ(s); setActive(0); inputRef.current && inputRef.current.focus(); }}>
                    {s}
                  </button>
                ))}
              </div>
            </React.Fragment>
          )}

          {q.trim() !== '' && hits.length === 0 && (
            <div style={searchStyles.empty}>
              {tr('search.empty.line1')}
              <br/>
              <span style={{ color: '#9a9893', fontSize: 12.5 }}>{tr('search.empty.line2')}</span>
            </div>
          )}

          {q.trim() !== '' && hits.length > 0 && GROUP_ORDER.map(g => {
            const items = grouped[g];
            if (!items || !items.length) return null;
            return (
              <div key={g}>
                <div style={searchStyles.groupHead}>
                  <span>{groupLabel[g]}</span>
                  <span style={searchStyles.groupCount}>{items.length}</span>
                </div>
                {items.map((hit) => {
                  const myIdx = flatIdx++;
                  return (
                    <ResultRow
                      key={hit.key}
                      hit={hit}
                      active={myIdx === active}
                      onHover={() => setActive(myIdx)}
                      onPick={() => pick(hit)}
                    />
                  );
                })}
              </div>
            );
          })}
        </div>

        <div style={searchStyles.foot}>
          <span>{tr('search.foot.lhs')}</span>
          <div style={searchStyles.footHints}>
            <span style={searchStyles.footHint}>
              <span style={searchStyles.footKbd}>↑</span>
              <span style={searchStyles.footKbd}>↓</span>
              {tr('search.foot.move')}
            </span>
            <span style={searchStyles.footHint}>
              <span style={searchStyles.footKbd}>↵</span>
              {tr('search.foot.select')}
            </span>
          </div>
        </div>
      </div>
    </div>
  );
}

window.SearchOverlay = SearchOverlay;
