import { useState, useMemo, useRef, useEffect } from 'react'
import kb from '../data/kb/index.js'
import { buildLoopIndex, matchLoopToProgression, findLoopPosition, chordRootPC } from '../lib/match'
import { NOTES, CHORD_TYPES } from '../lib/theory'
import RoadmapTrack from './RoadmapTrack'
import ChordDiagram from './ChordDiagram'
import MiniPiano from './MiniPiano'
import { pianoVoicingChain } from '../lib/piano'

// ─── JamGuide — the Roadmap bottom dock ───────────────────────────────────────
//
// The large bottom panel of JamBuddy. This is the SHELL (task L-02): the
// collapsed header bar, instrument + style tabs (derived from the KB registry),
// live loop → KB progression resolution, and a clearly-marked placeholder slot
// where the Roadmap visualization (RoadmapTrack + ChordDiagram, task D-02) will
// be wired in afterwards.
//
// This component does NOT import RoadmapTrack or ChordDiagram — sibling tasks
// build those in parallel; D-02 fills the [data-roadmap-slot] left here.
//
// Props (the contract D-02 relies on):
//   detectedProgression : string[] | null  — the live detected loop (chord names)
//   keyInfo             : { root, mode, confidence } | null  — effective key
//   chordHistory        : string[]          — committed chord history (for position)
//   bpm                 : number | null     — live tempo from the onset pipeline
//   currentChord        : string | undefined — most recent committed chord

// Display order for instrument tabs; availability is derived from the KB, not
// hardcoded — EXCEPT piano, which is always available: its voicings are COMPUTED
// from the progression's degrees+qualities via src/lib/piano.js (L-10/L-11), so
// no authored KB piano pack is required.
const INSTRUMENTS = [
  { id: 'guitar', label: 'Guitar', icon: '🎸' },
  { id: 'piano',  label: 'Piano',  icon: '🎹' },
  { id: 'bass',   label: 'Bass',   icon: '🎵' },
]
const COMPUTED_INSTRUMENTS = new Set(['piano'])

export default function JamGuide({ detectedProgression, keyInfo, chordHistory = [], bpm, currentChord, onFocusChord }) {
  const [open, setOpen] = useState(false)

  // Which instruments have at least one KB pack across the registry.
  const availableInstruments = useMemo(() => {
    const set = new Set()
    for (const style of Object.values(kb)) {
      for (const inst of Object.keys(style?.instruments ?? {})) set.add(inst)
    }
    return set
  }, [])

  // Style tabs straight from the KB registry, labelled via each style's meta.
  const styles = useMemo(
    () => Object.entries(kb).map(([id, style]) => ({ id, label: style?.meta?.label ?? id })),
    []
  )

  // Build the rotation-invariant loop index once.
  const kbIndex = useMemo(() => buildLoopIndex(kb), [])

  // Resolve the live loop → KB progression + rotation, and the current station.
  // Keyed on the loop input so this only recomputes when the loop changes.
  const loopKey = detectedProgression ? detectedProgression.join(',') : ''
  const match = useMemo(
    () => matchLoopToProgression(detectedProgression, kbIndex),
    [loopKey, kbIndex] // eslint-disable-line react-hooks/exhaustive-deps
  )
  const position = useMemo(
    () => findLoopPosition(chordHistory, detectedProgression),
    [chordHistory, loopKey] // eslint-disable-line react-hooks/exhaustive-deps
  )

  // Instrument tab: default to guitar (the only packs that exist today).
  const [instrument, setInstrument] = useState('guitar')

  // Style tab: follow the matched style, but let the user override.
  const [styleOverride, setStyleOverride] = useState(null)
  const activeStyle = styleOverride ?? (match.matched ? match.style : styles[0]?.id)

  // Header summary: matched progression name, or a listening hint.
  const matchedName = match.matched ? match.progression?.name : null
  const headerLabel = matchedName
    ? matchedName
    : (detectedProgression?.length ? 'mapping the changes…' : 'listening…')

  // ── Key root: keyInfo.root is a note NAME (e.g. "C"). RoadmapTrack and
  // ChordDiagram both want a pitch class 0–11. Convert once; default to C (0)
  // until a key is known so the roadmap still resolves to *some* spelling. ──
  const keyRoot = useMemo(() => {
    const pc = chordRootPC(keyInfo?.root)
    return pc >= 0 ? pc : 0
  }, [keyInfo?.root])
  const keyMode = keyInfo?.mode === 'minor' ? 'minor' : 'major'

  // ── Playhead reconciliation ────────────────────────────────────────────────
  // `position` from findLoopPosition is an index into the *detected* loop, which
  // can start on any rotation of the KB progression. RoadmapTrack renders the
  // progression in *canonical KB order* (degrees[0] first). They differ by
  // `match.rotation` — the loop index that aligns with KB degrees[0]. To map a
  // detected-loop index back to its canonical station:
  //     canonicalPos = ((position − rotation) mod n + n) mod n
  // Worked example — KB blues-turnaround [I VI ii V] looped as [ii V I VI]:
  //   rotation = 2 (loop index 2 = the "I" = KB degrees[0]).
  //   Playhead on the V (detected index 1) → ((1 − 2) % 4 + 4) % 4 = 3 = the V's
  //   canonical station. NOW lands on the right station. ✓
  const canonicalPos = useMemo(() => {
    if (!match.matched) return -1
    const n = match.progression?.degrees?.length ?? 0
    if (!n || typeof position !== 'number' || position < 0) return -1
    return (((position - match.rotation) % n) + n) % n
  }, [match.matched, match.progression, match.rotation, position])

  // ── Per-station voicings ────────────────────────────────────────────────────
  // Stations are canonical KB order — index i aligns 1:1 with
  // progression.degrees[i] (the same station order RoadmapTrack renders). Each
  // entry carries the station's chord identity ({rootPc, quality, label, rn} —
  // the tap-to-fretboard contract) plus an instrument-specific payload:
  //   guitar → `shape`: from the recommended KB guitar play (the first play);
  //            its `chords` array is canonical order too. No shape → graceful gap.
  //   piano  → `voicing`: COMPUTED via pianoVoicingChain over the whole loop in
  //            canonical order, so each station's register threads from the
  //            previous one (minimal movement between stations). The station's
  //            rootPc is attached so MiniPiano marks the root key ("R") reliably.
  const stationVoicings = useMemo(() => {
    if (!match.matched || (instrument !== 'guitar' && instrument !== 'piano')) return []
    const prog = match.progression
    const degrees = prog?.degrees ?? []
    const qualities = prog?.qualities ?? []
    const stations = degrees.map((deg, i) => {
      const rootPc = (((keyRoot + deg) % 12) + 12) % 12
      const noteName = NOTES[rootPc]
      const suffix = CHORD_TYPES[qualities[i]]?.suffix ?? ''
      return {
        shape: null,
        voicing: null,
        rootPc,
        quality: qualities[i] ?? 'maj',
        label: `${noteName}${suffix}`,
        rn: prog?.rn?.[i] ?? '',
      }
    })
    if (instrument === 'guitar') {
      const plays = kb[match.style]?.instruments?.guitar?.plays?.[prog?.id]
      const play = Array.isArray(plays) ? plays[0] : null
      const chords = play?.chords ?? []
      for (let i = 0; i < stations.length; i++) {
        stations[i].shape = chords[i]?.shape ?? null
      }
    } else {
      const chain = pianoVoicingChain(stations.map(({ rootPc, quality }) => ({ rootPc, quality })))
      for (let i = 0; i < stations.length; i++) {
        stations[i].voicing = chain[i] ? { ...chain[i], rootPc: stations[i].rootPc } : null
      }
    }
    return stations
  }, [match.matched, match.progression, match.style, instrument, keyRoot])

  // ── Tap-to-enlarge: which station's voicing is expanded (full diagram). ──
  const [selectedStation, setSelectedStation] = useState(null)
  // Reset the selection whenever the loop or style changes underneath us.
  useEffect(() => { setSelectedStation(null) }, [match.id, match.style, instrument])

  // ── Cross-link to the main Fretboard (D-03) ─────────────────────────────────
  // When a station is selected, report its {rootPc, quality} upward so the
  // Fretboard can light that chord's guide tones; clear (null) on deselect. The
  // reset effect above sets selectedStation → null on loop/style/instrument
  // change, which flows through here and clears the highlight too. Guarded so
  // the component still works standalone (onFocusChord optional).
  useEffect(() => {
    if (!onFocusChord) return
    const st = selectedStation != null ? stationVoicings[selectedStation] : null
    onFocusChord(st ? { rootPc: st.rootPc, quality: st.quality } : null)
  }, [selectedStation, stationVoicings, onFocusChord])

  // Clear the Fretboard highlight when JamGuide unmounts.
  useEffect(() => () => { onFocusChord?.(null) }, [onFocusChord])

  return (
    <div className="mb-3 bg-panel border border-border rounded-xl overflow-hidden">

      {/* ── Collapsed header bar (always visible) ── */}
      <button
        onClick={() => setOpen(v => !v)}
        className="w-full flex items-center justify-between px-4 py-3 text-left hover:bg-accent/5 transition-colors"
        aria-expanded={open}
      >
        <span className="flex items-center gap-2 min-w-0">
          <span className="text-base shrink-0">🎸</span>
          <span className="text-sm font-semibold text-accent shrink-0">Jam Guide</span>
          <span className="text-gray-600 shrink-0">—</span>
          <span className="text-sm text-gray-300 truncate">{headerLabel}</span>
          {match.matched && keyInfo?.root && (
            <span className="text-xs text-gray-500 shrink-0">
              in {keyInfo.root} {keyInfo.mode}
            </span>
          )}
        </span>
        <span className="text-gray-500 shrink-0 ml-3">{open ? '▲' : '▼'}</span>
      </button>

      {/* ── Expanded body (~70vh) ── */}
      {open && (
        <div className="border-t border-border flex flex-col" style={{ height: '70vh' }}>

          {/* ── Tab rows ── */}
          <div className="flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-2 border-b border-border">

            {/* Instrument tabs */}
            <div className="flex items-center gap-1">
              {INSTRUMENTS.map(inst => {
                // Computed instruments (piano) need no KB pack — always selectable.
                const enabled = COMPUTED_INSTRUMENTS.has(inst.id) || availableInstruments.has(inst.id)
                const active = enabled && inst.id === instrument
                return (
                  <button
                    key={inst.id}
                    onClick={() => enabled && setInstrument(inst.id)}
                    disabled={!enabled}
                    title={enabled ? inst.label : `${inst.label} packs coming soon`}
                    className={`px-3 py-1 rounded-lg text-sm font-medium transition-colors flex items-center gap-1.5 ${
                      active
                        ? 'bg-accent/20 border border-accent text-accent'
                        : enabled
                          ? 'border border-border text-gray-300 hover:border-gray-500 hover:text-gray-100'
                          : 'border border-border/50 text-gray-600 cursor-not-allowed'
                    }`}
                  >
                    <span>{inst.icon}</span>
                    <span>{inst.label}</span>
                    {!enabled && <span className="text-[10px] text-gray-700 ml-0.5">soon</span>}
                  </button>
                )
              })}
            </div>

            <div className="w-px h-5 bg-border shrink-0" />

            {/* Style tabs (from the KB registry) */}
            <div className="flex items-center gap-1 flex-wrap">
              {styles.map(style => {
                const active = style.id === activeStyle
                const isMatched = match.matched && style.id === match.style
                return (
                  <button
                    key={style.id}
                    onClick={() => setStyleOverride(style.id)}
                    className={`px-2.5 py-1 rounded-lg text-sm transition-colors ${
                      active
                        ? 'bg-accent/20 border border-accent text-accent font-semibold'
                        : 'border border-transparent text-gray-400 hover:text-gray-200 hover:border-border'
                    }`}
                  >
                    {style.label}
                    {isMatched && <span className="ml-1 text-accent/70" title="matches your loop">●</span>}
                  </button>
                )
              })}
            </div>
          </div>

          {/* ── Roadmap slot (D-02 assembly) ── */}
          <div data-roadmap-slot className="flex-1 min-h-0 p-4 overflow-auto">
            {match.matched ? (
              <RoadmapAssembly
                progression={match.progression}
                keyRoot={keyRoot}
                keyMode={keyMode}
                position={canonicalPos}
                bpm={bpm}
                stationVoicings={stationVoicings}
                selectedStation={selectedStation}
                onSelectStation={setSelectedStation}
              />
            ) : (
              <div
                className="h-full min-h-[200px] flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed border-border text-center"
              >
                <p className="text-sm text-gray-400">Play a few bars — I'll map the changes</p>
                <p className="text-xs text-gray-600">
                  {detectedProgression?.length
                    ? `Heard ${detectedProgression.join(' → ')}, but it doesn't match a ${activeStyle} pattern yet.`
                    : 'Roadmap renders here once a repeating loop is detected.'}
                </p>
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  )
}

// ─── RoadmapAssembly — the live panel body ────────────────────────────────────
//
// Composes RoadmapTrack (the improv highway) with a secondary voicing strip
// (one thumbnail per station, canonical KB order): ChordDiagram when a station
// carries a guitar `shape`, MiniPiano when it carries a computed piano `voicing`
// (L-11 — the piano tab). Tapping a thumbnail enlarges it to a full view inline. The active station auto-scrolls
// into view. Narrow viewports (< ~640px) reflow: the strip wraps and the whole
// panel scrolls vertically rather than forcing a wide horizontal layout.
function RoadmapAssembly({
  progression, keyRoot, keyMode, position, bpm,
  stationVoicings, selectedStation, onSelectStation,
}) {
  const stripRef = useRef(null)
  const activeRef = useRef(null)

  // Auto-scroll the active station's thumbnail into view as the playhead moves.
  // Prop-driven (off `position`) — no rAF loop tied to the audio thread.
  useEffect(() => {
    if (position < 0 || !activeRef.current) return
    activeRef.current.scrollIntoView({
      behavior: 'smooth', inline: 'center', block: 'nearest',
    })
  }, [position])

  const selected = selectedStation != null ? stationVoicings[selectedStation] : null

  return (
    <div className="flex h-full min-h-0 flex-col gap-4">
      {/* The improv highway — active-station styling + playhead live inside it. */}
      <RoadmapTrack
        progression={progression}
        keyRoot={keyRoot}
        keyMode={keyMode}
        position={position}
        bpm={bpm}
      />

      {/* Secondary voicing strip: one thumbnail per station, canonical order. */}
      {stationVoicings.length > 0 && (
        <section
          className="rounded-2xl border border-border bg-panel p-3"
          aria-label="Voicing thumbnails"
        >
          <h4 className="mb-2 text-[10px] font-semibold uppercase tracking-widest text-gray-500">
            Voicings · tap to enlarge
          </h4>
          <div
            ref={stripRef}
            className="flex flex-wrap gap-2 overflow-x-auto sm:flex-nowrap"
            role="list"
          >
            {stationVoicings.map((st, i) => {
              const isNow = i === position
              const isSelected = i === selectedStation
              return (
                <button
                  key={i}
                  ref={isNow ? activeRef : undefined}
                  type="button"
                  role="listitem"
                  aria-pressed={isSelected}
                  aria-current={isNow ? 'true' : undefined}
                  onClick={() => onSelectStation(isSelected ? null : i)}
                  title={`${st.label} — ${st.rn || `station ${i + 1}`}`}
                  className={
                    `flex shrink-0 flex-col items-center gap-1 rounded-lg border p-2 outline-none transition ` +
                    `focus-visible:ring-2 focus-visible:ring-accent ` +
                    (isNow
                      ? 'border-accent bg-accent/10 ring-1 ring-accent'
                      : isSelected
                        ? 'border-accent/60 bg-accent/5'
                        : 'border-border bg-surface hover:border-gray-500')
                  }
                  style={{ opacity: isNow ? 1 : 0.85 }}
                >
                  {st.voicing ? (
                    <>
                      <MiniPiano voicing={st.voicing} size="thumb" />
                      {/* MiniPiano has no built-in chord label; mirror ChordDiagram's
                          thumb label (text-gray-300, 9px) so the station stays named. */}
                      <span className="text-gray-300 leading-none" style={{ fontSize: 9 }}>
                        {st.label}
                      </span>
                    </>
                  ) : (
                    <ChordDiagram
                      shape={st.shape}
                      keyRoot={keyRoot}
                      rootPc={st.rootPc}
                      size="thumb"
                      label={st.label}
                    />
                  )}
                  {st.rn && (
                    <span className="text-[9px] font-medium uppercase tracking-wide text-gray-500">
                      {st.rn}
                    </span>
                  )}
                </button>
              )
            })}
          </div>

          {/* Enlarged view of the tapped station (deferred fretboard cross-link
              lives here instead — see D-02 return note). */}
          {selected && (
            <div className="mt-3 flex flex-col items-center gap-2 border-t border-border pt-3">
              {selected.voicing ? (
                <>
                  <MiniPiano voicing={selected.voicing} size="full" />
                  <span className="text-gray-300 leading-none" style={{ fontSize: 12 }}>
                    {`${selected.label}${selected.rn ? ` · ${selected.rn}` : ''}`}
                  </span>
                </>
              ) : (
                <ChordDiagram
                  shape={selected.shape}
                  keyRoot={keyRoot}
                  rootPc={selected.rootPc}
                  size="full"
                  label={`${selected.label}${selected.rn ? ` · ${selected.rn}` : ''}`}
                />
              )}
              <button
                type="button"
                onClick={() => onSelectStation(null)}
                className="text-[11px] text-gray-500 underline-offset-2 hover:text-gray-300 hover:underline focus-visible:ring-2 focus-visible:ring-accent rounded outline-none"
              >
                close
              </button>
            </div>
          )}
        </section>
      )}
    </div>
  )
}
