// timeline-view.jsx — horizontal timeline with composer lifespan bars

const { useMemo, useRef, useEffect, useState } = React;

function TimelineView({ density, onSelectComposer, onSelectEra, eraFilter, setEraFilter, countryFilter, setCountryFilter, rankLimit, setRankLimit, focusId, setFocusId }) {
  const allComposers = window.COMPOSERS;
  const composers = allComposers.filter(c => c.rank <= rankLimit);
  const eras = window.ERAS;

  // A bar is dimmed when it doesn't match the active era and/or country filter.
  const isDimmed = (c) =>
    (eraFilter && c.era !== eraFilter) ||
    (countryFilter && c.nation !== countryFilter);

  // Distinct countries among the visible (rank-filtered) composers, with flag + count.
  const countries = useMemo(() => {
    const m = new Map();
    for (const c of composers) {
      if (!m.has(c.nation)) m.set(c.nation, { nation: c.nation, flag: c.flag, count: 0 });
      m.get(c.nation).count++;
    }
    return [...m.values()].sort((a, b) => b.count - a.count || a.nation.localeCompare(b.nation));
  }, [composers]);

  const ROW_H = density === 'tight' ? 28 : density === 'loose' ? 38 : 32;
  const LABEL_GUARD = 240; // approx label width to avoid overlap

  // Range
  const FIRST_YEAR = 1560;
  const LAST_YEAR  = 1980;
  const totalYears = LAST_YEAR - FIRST_YEAR;

  // Zoom: pixels per year, set by a slider. Default (zoom===null) fits the whole timeline.
  const scrollRef = useRef(null);
  const [containerW, setContainerW] = useState(0);
  const [zoom, setZoom] = useState(null);
  useEffect(() => {
    const measure = () => { if (scrollRef.current) setContainerW(scrollRef.current.clientWidth); };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);
  const fitPx = containerW ? Math.max(1.5, (containerW - 300) / totalYears) : 6;
  const PX_PER_YEAR = zoom == null ? fitPx : zoom;
  const canvasWidth = totalYears * PX_PER_YEAR;

  const yearToX = (y) => (y - FIRST_YEAR) * PX_PER_YEAR;

  // Lane assignment: greedy
  const lanes = useMemo(() => {
    // sort by birth year
    const sorted = [...composers].sort((a, b) => a.born - b.born);
    const laneEnds = []; // x-position where each lane ends (rightmost x used)
    const out = [];
    for (const c of sorted) {
      const startX = yearToX(c.born);
      const endX   = yearToX(c.died) + LABEL_GUARD;
      let laneIdx = 0;
      while (laneIdx < laneEnds.length && laneEnds[laneIdx] > startX) laneIdx++;
      laneEnds[laneIdx] = endX;
      out.push({ ...c, lane: laneIdx, startX, endX });
    }
    return out;
  }, [composers, PX_PER_YEAR]);

  const laneCount = useMemo(
    () => lanes.reduce((m, c) => Math.max(m, c.lane), 0) + 1,
    [lanes]
  );

  // Year ticks
  const ticks = [];
  for (let y = Math.ceil(FIRST_YEAR / 10) * 10; y <= LAST_YEAR; y += 10) {
    ticks.push({ year: y, major: y % 50 === 0, x: yearToX(y) });
  }

  // ─── Keyboard navigation ─────────────────────────────────────
  // Navigable set = bars that respond to clicks (dimmed/other-era bars excluded).
  const navList = useMemo(
    () => lanes.filter(c => !isDimmed(c)),
    [lanes, eraFilter, countryFilter]
  );

  // Drop focus if the focused composer left the visible/navigable set (filter/rank change).
  useEffect(() => {
    if (focusId && !navList.some(c => c.id === focusId)) setFocusId(null);
  }, [navList, focusId]);

  // Scroll the focused bar into view whenever it changes.
  useEffect(() => {
    if (!focusId || !scrollRef.current) return;
    const el = scrollRef.current.querySelector(`[data-cid="${focusId}"]`);
    if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
  }, [focusId]);

  useEffect(() => {
    const onKey = (e) => {
      // Don't hijack typing / the rank slider.
      const ae = document.activeElement;
      if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'TEXTAREA' || ae.tagName === 'SELECT' || ae.isContentEditable)) return;
      if (!navList.length) return;
      const keys = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Enter'];
      if (!keys.includes(e.key)) return;

      const cur = focusId ? navList.find(c => c.id === focusId) : null;

      // First keypress with no focus: pick the bar nearest the viewport centre.
      if (!cur) {
        if (e.key === 'Enter') return;
        e.preventDefault();
        const sc = scrollRef.current;
        const center = sc ? sc.scrollLeft + sc.clientWidth / 2 : 0;
        let best = navList[0], bestD = Infinity;
        for (const c of navList) {
          const mid = yearToX((c.born + c.died) / 2);
          const d = Math.abs(mid - center);
          if (d < bestD) { bestD = d; best = c; }
        }
        setFocusId(best.id);
        return;
      }

      e.preventDefault();

      if (e.key === 'Enter') { onSelectComposer(cur.id); return; }

      if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
        // navList is birth-sorted; step chronologically.
        const idx = navList.indexOf(cur);
        const next = navList[idx + (e.key === 'ArrowRight' ? 1 : -1)];
        if (next) setFocusId(next.id);
        return;
      }

      // Up / Down: move to the bar most directly above / below — i.e. the one in a
      // lane in that direction whose time position (born) is closest to the current.
      const up = e.key === 'ArrowUp';
      const cands = navList.filter(c => (up ? c.lane < cur.lane : c.lane > cur.lane));
      if (!cands.length) return;
      cands.sort((a, b) =>
        Math.abs(a.born - cur.born) - Math.abs(b.born - cur.born) ||
        Math.abs(a.lane - cur.lane) - Math.abs(b.lane - cur.lane)
      );
      setFocusId(cands[0].id);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [navList, focusId, onSelectComposer]);

  const eraAccent = (id) => `var(--era-${id})`;

  return (
    <div className="tl-wrap">
      <div className="tl-legend">
        <button
          className={'tl-legend-item' + (!eraFilter ? ' is-active' : '')}
          onClick={() => setEraFilter(null)}
          style={{ background: 'transparent', border: 0 }}
        >
          <span className="swatch" style={{ background: 'var(--ink-mute)' }}></span>
          All eras
        </button>
        {eras.map(e => (
          <button
            key={e.id}
            className={'tl-legend-item' + (eraFilter === e.id ? ' is-active' : '')}
            onClick={() => setEraFilter(eraFilter === e.id ? null : e.id)}
            style={{ background: 'transparent', border: 0 }}
          >
            <span className="swatch" style={{ background: eraAccent(e.id) }}></span>
            {e.name}
          </button>
        ))}
        <div className="tl-rank-seg">
          <span className="tl-rank-label">Top</span>
          {[10, 25, 50].map(n => (
            <button
              key={n}
              className={'tl-rank-btn' + (rankLimit === n ? ' is-active' : '')}
              onClick={() => setRankLimit(n)}
            >
              {n}
            </button>
          ))}
        </div>
        <div className="tl-zoom">
          <span className="tl-rank-label">Zoom</span>
          <span className="tl-zoom-ico">⊟</span>
          <input
            type="range"
            min={fitPx}
            max={16}
            step={0.1}
            value={PX_PER_YEAR}
            onChange={e => setZoom(Number(e.target.value))}
          />
          <span className="tl-zoom-ico">⊞</span>
          {zoom != null && (
            <button className="tl-zoom-fit" onClick={() => setZoom(null)} title="Fit all">Fit</button>
          )}
        </div>
        <span className="tl-kbd-hint">
          <kbd>←</kbd><kbd>↑</kbd><kbd>↓</kbd><kbd>→</kbd> 瀏覽 · <kbd>↵</kbd> 開啟
        </span>
      </div>

      <div className="tl-legend tl-legend-country">
        <button
          className={'tl-legend-item' + (!countryFilter ? ' is-active' : '')}
          onClick={() => setCountryFilter(null)}
          style={{ background: 'transparent', border: 0 }}
        >
          <span className="tl-legend-flag">🌐</span>
          All countries
        </button>
        {countries.map(c => (
          <button
            key={c.nation}
            className={'tl-legend-item' + (countryFilter === c.nation ? ' is-active' : '')}
            onClick={() => setCountryFilter(countryFilter === c.nation ? null : c.nation)}
            style={{ background: 'transparent', border: 0 }}
          >
            <span className="tl-legend-flag">{c.flag}</span>
            {c.nation}
          </button>
        ))}
      </div>

      <div className="tl-scroll" ref={scrollRef}>
        <div className="tl-canvas" style={{ width: canvasWidth + 280 + 80 }}>
          {/* Year axis */}
          <div className="tl-axis">
            <div className="tl-axis-track" style={{ width: canvasWidth }}>
              {ticks.map(t => (
                <div
                  key={t.year}
                  className={'tl-tick' + (t.major ? ' major' : '')}
                  style={{ left: t.x }}
                >
                  <div className="tl-tick-line"></div>
                  {t.major && <div className="tl-tick-label">{t.year}</div>}
                </div>
              ))}
            </div>

            {/* Era bands */}
            <div className="tl-eras" style={{ width: canvasWidth, marginTop: 8 }}>
              {eras.map(e => {
                const x = yearToX(e.start);
                const w = (e.end - e.start) * PX_PER_YEAR;
                return (
                  <div
                    key={e.id}
                    className={'tl-era-band' + (eraFilter === e.id ? ' is-active' : '') + (eraFilter && eraFilter !== e.id ? ' is-dim' : '')}
                    style={{ left: x, width: w, color: eraAccent(e.id) }}
                    onClick={() => setEraFilter(eraFilter === e.id ? null : e.id)}
                    title={eraFilter === e.id ? `Show all eras` : `Highlight ${e.name}`}
                  >
                    {e.name}
                  </div>
                );
              })}
            </div>
          </div>

          {/* Lanes */}
          <div
            className="tl-lanes"
            style={{ height: laneCount * ROW_H + 20, width: canvasWidth }}
          >
            {/* vertical guides */}
            <div className="tl-grid">
              {ticks.filter(t => t.major).map(t => (
                <div
                  key={t.year}
                  className="tl-grid-line major"
                  style={{ left: t.x }}
                ></div>
              ))}
            </div>

            {lanes.map(c => {
              const x = yearToX(c.born);
              const w = (c.died - c.born) * PX_PER_YEAR;
              const peak = Math.min(c.died, c.born + 40);
              const nodeX = yearToX(peak) - x;
              const isDim = isDimmed(c);
              const surname = c.name.split(' ').slice(-1)[0];
              const given   = c.name.slice(0, c.name.length - surname.length).trim();
              return (
                <div
                  key={c.id}
                  data-cid={c.id}
                  className={'tl-bar' + (isDim ? ' is-dim' : '') + (c.id === focusId ? ' is-focus' : '')}
                  style={{
                    left: x,
                    top: c.lane * ROW_H + 8,
                    width: w,
                    color: eraAccent(c.era),
                  }}
                  onClick={() => onSelectComposer(c.id)}
                >
                  <div className="tl-bar-line"></div>
                  <div className="tl-bar-node" style={{ left: nodeX }}></div>
                  <div className="tl-bar-label">
                    {given && <span className="given">{given}</span>}
                    <span className="surname">{surname}</span>
                    <span className="tl-flag">{c.flag}</span>
                    <span className="dates">{c.born}–{c.died}</span>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

window.TimelineView = TimelineView;
