/**
 * The rack — click cells, hear a groove, paste it into a `.tidal` file.
 *
 * A kit is not validated one sample at a time. The questions that decide whether a cut
 * was worth making are "do these two loops sit together", "does this vocal survive on
 * top of that break", "is this bass the same tempo as it claims" — and none of them can
 * be answered in a file browser. So the rack is a small step sequencer whose grid means
 * exactly what Tidal means:
 *
 * - A **bar-loop** row has one cell per bar of the phrase, but only on multiples of its
 *   own length: a 4-bar loop in an 8-bar phrase has two entries, not eight, because
 *   starting it on bar 3 would play its bar 1 under the phrase's bar 4. The cells
 *   translate to `mask "t f"`.
 * - A **chop** row has 16 steps in one bar, repeated. The cells translate to the
 *   structure string `s "x ~ ~ x …"`.
 *
 * Everything plays at the deck's tempo via `loopAt`, so the layering you hear is the
 * layering you get, and the export is not an approximation of the session — it is the
 * same two numbers written in Tidal's syntax.
 */
import { useEffect, useState } from 'react'
import { Trash2, Copy, Volume2, VolumeX, Check } from 'lucide-react'
import { CHOP_STEPS, PHRASE_BARS, type Voice } from './engine'
import { familyColor } from './types'
import { toTidal, toStrudel, type ExportRow } from './tidal'

export type RackRow = ExportRow & { color: string }

type Props = {
  rows: RackRow[]
  bpm: number
  playing: boolean
  position: number            // bar index within the phrase, -1 when stopped
  onChange: (id: string, patch: Partial<Voice>) => void
  onRemove: (id: string) => void
  onClear: () => void
}

export function Rack({ rows, bpm, playing, position, onChange, onRemove, onClear }: Props) {
  const [tab, setTab] = useState<'tidal' | 'strudel'>('tidal')
  const [copied, setCopied] = useState(false)
  useEffect(() => { if (copied) { const t = setTimeout(() => setCopied(false), 1200); return () => clearTimeout(t) } }, [copied])

  const code = tab === 'tidal' ? toTidal(rows, bpm) : toStrudel(rows, bpm)

  if (!rows.length) {
    return (
      <div className="p-4 text-[12px] text-ink-faint max-w-2xl">
        The rack is empty. Add samples with <span className="font-mono">+</span> — or press
        <span className="font-mono"> a </span>on a focused row — then hit play. Loops are
        stretched to the deck tempo with <span className="font-mono">loopAt</span>, which is
        what Tidal will do, so what you hear here is what the rig plays.
      </div>
    )
  }

  return (
    <div className="flex min-h-0 flex-1">
      <div className="flex-1 overflow-y-auto">
        {rows.map(({ voice: v, kit, n, family }) => {
          const isChop = v.bars === 0
          const cells = isChop ? CHOP_STEPS : PHRASE_BARS
          const stride = isChop ? 1 : v.bars
          const col = familyColor(family)
          return (
            <div key={v.id} className="flex items-center gap-2 px-2 py-1 border-b border-hairline/60">
              <button onClick={() => onChange(v.id, { muted: !v.muted })}
                      className={`p-1 rounded-sm hover:bg-overlay ${v.muted ? 'text-ink-faint' : 'text-ink-muted'}`}
                      title={v.muted ? 'unmute' : 'mute'}>
                {v.muted ? <VolumeX size={13} /> : <Volume2 size={13} />}
              </button>

              <div className="w-40 min-w-0">
                <div className="truncate text-[12px] leading-tight" style={{ color: v.muted ? 'var(--color-ink-faint)' : col }}>
                  {v.id.split('/')[1]}
                </div>
                <div className="truncate text-[10px] text-ink-faint leading-tight font-mono">
                  {kit} · n {n} · {isChop ? 'chop' : `loopAt ${v.bars}`}
                </div>
              </div>

              {/* the grid. A cell is a bar (or a 16th, for a chop) of the phrase. */}
              <div className="flex flex-1 gap-[3px]">
                {Array.from({ length: cells }, (_, i) => {
                  const on = !!v.cells[i]
                  const usable = i % stride === 0
                  // where the playhead is, in this row's own units
                  const live = playing && (isChop
                    ? false                       // a 16th playhead would strobe; the bar rule is enough
                    : position >= 0 && Math.floor(position / stride) * stride === i)
                  if (!usable) return <div key={i} className="flex-1" />
                  return (
                    <button key={i}
                            onClick={() => {
                              const next = [...v.cells]
                              next[i] = !on
                              onChange(v.id, { cells: next })
                            }}
                            style={{
                              flex: stride,
                              background: on ? col : 'var(--color-raised)',
                              opacity: v.muted ? 0.35 : 1,
                              boxShadow: live ? 'inset 0 0 0 1.5px var(--color-ink)' : undefined,
                            }}
                            className="h-6 rounded-sm border border-hairline/60 hover:brightness-125" />
                  )
                })}
              </div>

              <input type="range" min={0} max={1.5} step={0.01} value={v.gain}
                     onChange={(e) => onChange(v.id, { gain: Number(e.target.value) })}
                     className="sx-range w-20" style={{ ['--fill' as string]: `${(v.gain / 1.5) * 100}%` }}
                     title={`fader ${v.gain.toFixed(2)} (linear)`} />

              <button onClick={() => onRemove(v.id)} className="p-1 rounded-sm text-ink-faint hover:bg-overlay hover:text-blocked">
                <Trash2 size={13} />
              </button>
            </div>
          )
        })}
      </div>

      {/* the payoff: the same groove as source you can paste into a live file */}
      <div className="w-[27rem] shrink-0 border-l border-hairline flex flex-col">
        <div className="flex items-center gap-1 px-2 py-1 border-b border-hairline">
          {(['tidal', 'strudel'] as const).map((t) => (
            <button key={t} onClick={() => setTab(t)}
                    className={`px-2 py-0.5 text-[11px] rounded-sm font-mono
                                ${tab === t ? 'bg-overlay text-ink' : 'text-ink-faint hover:text-ink-muted'}`}>
              {t}
            </button>
          ))}
          <div className="flex-1" />
          <button onClick={() => { void navigator.clipboard.writeText(code); setCopied(true) }}
                  className="flex items-center gap-1 px-2 py-0.5 text-[11px] rounded-sm text-ink-muted hover:bg-overlay">
            {copied ? <Check size={12} className="text-ready" /> : <Copy size={12} />}
            {copied ? 'copied' : 'copy'}
          </button>
          <button onClick={onClear} className="px-2 py-0.5 text-[11px] rounded-sm text-ink-faint hover:bg-overlay hover:text-blocked">
            clear
          </button>
        </div>
        <pre className="flex-1 overflow-auto p-2 text-[11px] leading-[1.45] font-mono text-ink-muted whitespace-pre">
          {code}
        </pre>
      </div>
    </div>
  )
}
