// frontend/src/pages/melodies/MelodyComposer.jsx
// Visual step-sequencer for building bell melodies.
// Logic ported from _archive/melodies/MelodyComposer.jsx — no styling copied.

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import PageHeader from '@/components/ui/PageHeader'
import Button from '@/components/ui/Button'
import Card from '@/components/ui/Card'
import DeployArchetypeModal from '@/modals/bellcloud/melodies/DeployArchetypeModal'
import { useToast } from '@/components/ui/Toast'
import api from '@/lib/api'

// ─── Constants ───────────────────────────────────────────────────────────────

const MAX_NOTES    = 16
const NOTE_LABELS  = 'ABCDEFGHIJKLMNOP'

// ─── Pure helpers (logic only) ────────────────────────────────────────────────

function bellFrequency(bellNumber) {
  return 880 * Math.pow(2, -((bellNumber - 1) / 8))
}

function stepToNotation(stepValue) {
  if (!stepValue) return '0'
  const active = []
  for (let bit = 0; bit < 16; bit++) {
    if (stepValue & (1 << bit)) active.push(bit + 1)
  }
  return active.join('+')
}

function stepToHex(stepValue) {
  return `0x${(stepValue >>> 0).toString(16).toUpperCase().padStart(4, '0')}`
}

function msToBpm(ms) {
  return Math.max(1, Math.round(60000 / Math.max(1, Number(ms) || 1)))
}

function bpmToMs(bpm) {
  return Math.max(20, Math.round(60000 / Math.max(1, Number(bpm) || 1)))
}

function interpolateHue(t) {
  const stops = [[0.0, 190], [0.24, 140], [0.5, 56], [0.82, 30], [1.0, 0]]
  for (let i = 0; i < stops.length - 1; i++) {
    const [aPos, aHue] = stops[i]
    const [bPos, bHue] = stops[i + 1]
    if (t >= aPos && t <= bPos) {
      const local = (t - aPos) / (bPos - aPos || 1)
      return aHue + (bHue - aHue) * local
    }
  }
  return stops[stops.length - 1][1]
}

function noteDotColor(noteNumber, palette) {
  const n = Number(noteNumber || 1)
  const custom = palette?.[n - 1]
  if (custom) return custom
  const t = Math.min(1, Math.max(0, (n - 1) / 15))
  return `hsl(${interpolateHue(t)}, 78%, 68%)`
}

function noteDotGlow(noteNumber, palette) {
  const n = Number(noteNumber || 1)
  const custom = palette?.[n - 1]
  if (custom) return `${custom}66`
  const t = Math.min(1, Math.max(0, (n - 1) / 15))
  return `hsla(${interpolateHue(t)}, 78%, 56%, 0.45)`
}

function playStep(audioCtx, stepValue, noteDurationMs) {
  if (!audioCtx) return
  const now      = audioCtx.currentTime
  const duration = Math.max(10, noteDurationMs) / 1000
  const fadeIn   = 0.005
  const fadeOut  = Math.min(0.03, duration / 2)

  for (let bit = 0; bit < 16; bit++) {
    if (stepValue & (1 << bit)) {
      const freq = bellFrequency(bit + 1)
      const osc  = audioCtx.createOscillator()
      const gain = audioCtx.createGain()
      osc.type = 'sine'
      osc.frequency.setValueAtTime(freq, now)
      gain.gain.setValueAtTime(0, now)
      gain.gain.linearRampToValueAtTime(0.3, now + fadeIn)
      gain.gain.setValueAtTime(0.3, now + Math.max(duration - fadeOut, fadeIn + 0.001))
      gain.gain.linearRampToValueAtTime(0, now + duration)
      osc.connect(gain)
      gain.connect(audioCtx.destination)
      osc.start(now)
      osc.stop(now + duration)
    }
  }
}

function csvToSteps(csv) {
  if (!csv?.trim()) return null
  return csv.trim().split(',').map((token) => {
    const parts = token.split('+')
    let val = 0
    for (const p of parts) {
      const n = parseInt(p.trim(), 10)
      if (!isNaN(n) && n >= 1 && n <= 16) val |= (1 << (n - 1))
    }
    return val
  })
}

// ─── Component ────────────────────────────────────────────────────────────────

export default function MelodyComposer() {
  const navigate = useNavigate()
  const { state: routeState } = useLocation()
  const { toast } = useToast()

  const loadedArchetype = routeState?.archetype ?? null

  // ── Initial state from archetype ──────────────────────────────────────────
  const initialSteps = () => {
    if (loadedArchetype?.steps) {
      const parsed = csvToSteps(loadedArchetype.steps)
      if (parsed?.length) return parsed
    }
    return Array.from({ length: 16 }, () => 0)
  }

  const initialNoteCount = () => {
    if (loadedArchetype?.steps) {
      const parsed = csvToSteps(loadedArchetype.steps)
      if (parsed?.length) {
        let maxBit = 0
        for (const v of parsed) {
          for (let b = 15; b >= 0; b--) {
            if (v & (1 << b)) { maxBit = Math.max(maxBit, b + 1); break }
          }
        }
        return Math.max(8, maxBit)
      }
    }
    return 8
  }

  // ── State ─────────────────────────────────────────────────────────────────
  const [steps,          setSteps]          = useState(initialSteps)
  const [noteCount,      setNoteCount]      = useState(initialNoteCount)
  const [stepDelayMs,    setStepDelayMs]    = useState(280)
  const [noteDurationMs, setNoteDurationMs] = useState(110)
  const [measureEvery,   setMeasureEvery]   = useState(4)
  const [loopEnabled,    setLoopEnabled]    = useState(true)
  const [isPlaying,      setIsPlaying]      = useState(false)
  const [currentStep,    setCurrentStep]    = useState(-1)
  const [noteColors,     setNoteColors]     = useState([])
  const [stepMenuIndex,  setStepMenuIndex]  = useState(null)
  const [deployOpen,     setDeployOpen]     = useState(false)
  const [deployMode,     setDeployMode]     = useState('new')

  // ── Refs ──────────────────────────────────────────────────────────────────
  const audioCtxRef      = useRef(null)
  const playbackRef      = useRef(null)
  const stepsRef         = useRef(steps)
  const stepDelayRef     = useRef(stepDelayMs)
  const noteDurationRef  = useRef(noteDurationMs)
  const loopEnabledRef   = useRef(loopEnabled)
  const stepMenuRef      = useRef(null)

  // Keep refs in sync
  useEffect(() => { stepsRef.current        = steps         }, [steps])
  useEffect(() => { stepDelayRef.current    = stepDelayMs   }, [stepDelayMs])
  useEffect(() => { noteDurationRef.current = noteDurationMs}, [noteDurationMs])
  useEffect(() => { loopEnabledRef.current  = loopEnabled   }, [loopEnabled])

  // Close step context menu on outside click
  useEffect(() => {
    if (stepMenuIndex == null) return
    const onDocClick = (e) => {
      if (stepMenuRef.current && !stepMenuRef.current.contains(e.target)) {
        setStepMenuIndex(null)
      }
    }
    document.addEventListener('mousedown', onDocClick)
    return () => document.removeEventListener('mousedown', onDocClick)
  }, [stepMenuIndex])

  // Load custom note colours from melody settings
  useEffect(() => {
    let canceled = false
    api.get('/settings/melody')
      .then((s) => { if (!canceled) setNoteColors((s?.note_assignment_colors || []).slice(0, 16)) })
      .catch(() => { if (!canceled) setNoteColors([]) })
    return () => { canceled = true }
  }, [])

  // ── Playback ──────────────────────────────────────────────────────────────
  const ensureAudioContext = useCallback(() => {
    if (!audioCtxRef.current || audioCtxRef.current.state === 'closed') {
      const AudioCtx = window.AudioContext || /** @type {any} */ (window).webkitAudioContext
    audioCtxRef.current = new AudioCtx()
    }
    if (audioCtxRef.current.state === 'suspended') audioCtxRef.current.resume()
    return audioCtxRef.current
  }, [])

  const stopPlayback = useCallback(() => {
    if (playbackRef.current?.timer) clearTimeout(playbackRef.current.timer)
    playbackRef.current = null
    setIsPlaying(false)
    setCurrentStep(-1)
  }, [])

  const scheduleStep = useCallback((stepIndex) => {
    const currentSteps = stepsRef.current
    if (!currentSteps.length) { stopPlayback(); return }

    const ctx       = ensureAudioContext()
    const nextIndex = stepIndex % currentSteps.length
    setCurrentStep(nextIndex)
    playStep(ctx, currentSteps[nextIndex], noteDurationRef.current)

    const isLastStep    = nextIndex >= currentSteps.length - 1
    const shouldContinue = !isLastStep || loopEnabledRef.current

    if (!shouldContinue) {
      playbackRef.current = { timer: setTimeout(() => stopPlayback(), stepDelayRef.current) }
      return
    }
    playbackRef.current = {
      timer: setTimeout(() => scheduleStep(isLastStep ? 0 : nextIndex + 1), stepDelayRef.current),
    }
  }, [ensureAudioContext, stopPlayback])

  useEffect(() => () => stopPlayback(), [stopPlayback])

  const handlePlay = () => {
    if (!stepsRef.current.length) return
    setIsPlaying(true)
    scheduleStep(0)
  }

  // ── Grid editing ──────────────────────────────────────────────────────────
  const toggleCell = (noteIndex, stepIndex) => {
    const bit = 1 << noteIndex
    setSteps((prev) => {
      const next = [...prev]
      next[stepIndex] = (next[stepIndex] || 0) ^ bit
      return next
    })
  }

  const addStep    = () => setSteps((prev) => [...prev, 0])
  const removeStep = () => setSteps((prev) => {
    if (prev.length <= 1) return prev
    const next = prev.slice(0, prev.length - 1)
    if (currentStep >= next.length) setCurrentStep(next.length - 1)
    return next
  })
  const addNote    = () => setNoteCount((prev) => Math.min(MAX_NOTES, prev + 1))
  const removeNote = () => setNoteCount((prev) => {
    if (prev <= 1) return prev
    const next = prev - 1
    setSteps((s) => s.map((v) => v & ((1 << next) - 1)))
    return next
  })
  const clearAll      = () => setSteps((prev) => prev.map(() => 0))
  const insertStepAt  = (index) => {
    setSteps((prev) => { const n = [...prev]; n.splice(index, 0, 0); return n })
    setCurrentStep((prev) => (prev >= index ? prev + 1 : prev))
  }
  const deleteStepAt  = (index) => {
    setSteps((prev) => {
      if (prev.length <= 1) return prev
      const n = [...prev]; n.splice(index, 1); return n
    })
    setCurrentStep((prev) => {
      if (prev < 0) return prev
      if (prev === index) return -1
      if (prev > index) return prev - 1
      return prev
    })
  }

  // ── Deploy ────────────────────────────────────────────────────────────────
  const openDeploy = (mode) => { setDeployMode(mode); setDeployOpen(true) }

  const handleDeploySuccess = (result, mode) => {
    setDeployOpen(false)
    toast.success(
      mode === 'update' ? 'Archetype Updated' : 'Archetype Deployed',
      `"${result.name}" saved successfully.`,
    )
    if (result?.id) navigate(`/melodies/archetypes/${result.id}`)
  }

  // ── Derived ───────────────────────────────────────────────────────────────
  const speedBpm = msToBpm(stepDelayMs)

  const activeBells = useMemo(() => {
    if (currentStep < 0 || !steps[currentStep]) return []
    const active = []
    for (let bit = 0; bit < noteCount; bit++) {
      if (steps[currentStep] & (1 << bit)) active.push(bit + 1)
    }
    return active
  }, [currentStep, noteCount, steps])

  const csvOutput    = `{${steps.map(stepToNotation).join(',')}}`
  const progmemOutput = `const uint16_t PROGMEM melody_builtin_custom[] = {\n  ${steps.map(stepToHex).join(', ')}\n};`

  // ─── Render ───────────────────────────────────────────────────────────────
  return (
    <div className="page-wrapper">

      {/* ── Page header ─────────────────────────────────────────────────── */}
      <PageHeader
        title="Melody Composer"
        subtitle="Build bell-step melodies visually. Notes map to bell numbers (1–16)."
      >
        {loadedArchetype ? (
          <>
            <Button variant="secondary" onClick={() => openDeploy('new')}>Deploy as New</Button>
            <Button variant="primary"   onClick={() => openDeploy('update')}>Update Archetype</Button>
          </>
        ) : (
          <Button variant="primary" onClick={() => openDeploy('new')}>Deploy Archetype</Button>
        )}
      </PageHeader>

      {/* ── Editing-archetype banner ─────────────────────────────────────── */}
      {loadedArchetype && (
        <div style={{
          display: 'flex',
          alignItems: 'center',
          gap: 'var(--space-3)',
          backgroundColor: 'var(--color-primary-subtle)',
          border: '1px solid var(--color-primary)',
          borderRadius: 'var(--radius-lg)',
          padding: 'var(--space-3) var(--space-4)',
        }}>
          <span style={{
            fontSize: 'var(--font-size-xs)',
            fontWeight: 'var(--font-weight-semibold)',
            textTransform: 'uppercase',
            letterSpacing: 'var(--tracking-wide)',
            color: 'var(--color-primary)',
          }}>
            Editing Archetype
          </span>
          <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-primary)' }}>
            {loadedArchetype.name}
          </span>
          <span style={{ color: 'var(--color-text-muted)', fontSize: 'var(--font-size-xs)' }}>·</span>
          <span style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
            {loadedArchetype.id}
          </span>
          <Button
            variant="ghost"
                       style={{ marginLeft: 'auto' }}
            onClick={() => navigate('/melodies/composer', { replace: true, state: null })}
          >
            Clear
          </Button>
        </div>
      )}

      {/* ── Transport + controls ─────────────────────────────────────────── */}
      <Card padding={false}>
        <div style={{ padding: 'var(--space-6) var(--space-8)', display: 'flex', flexDirection: 'column', gap: 0 }}>
        {/* Row 1 — step/note controls + status + deploy */}
        <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 'var(--space-3)' }}>
          {/* Step controls */}
          <div style={{ display: 'flex', gap: 'var(--space-1)', alignItems: 'center' }}>
            <Button variant="secondary" onClick={addStep}>+ Step</Button>
            <Button variant="secondary" onClick={removeStep}>− Step</Button>
          </div>

          <span style={{ color: 'var(--color-border-strong)', fontSize: 'var(--font-size-sm)' }}>|</span>

          {/* Note controls */}
          <div style={{ display: 'flex', gap: 'var(--space-1)', alignItems: 'center' }}>
            <Button variant="secondary" onClick={addNote}    disabled={noteCount >= MAX_NOTES}>+ Note</Button>
            <Button variant="secondary" onClick={removeNote} disabled={noteCount <= 1}>− Note</Button>
          </div>

          <span style={{ color: 'var(--color-border-strong)', fontSize: 'var(--font-size-sm)' }}>|</span>

          <Button variant="danger" onClick={clearAll}>Clear All</Button>

          {/* Status readout */}
          <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
            <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', fontFamily: 'var(--font-family-mono)' }}>
              {currentStep >= 0
                ? `Step ${currentStep + 1}/${steps.length}${activeBells.length ? ` · bells ${activeBells.join(', ')}` : ' · silence'}`
                : `${steps.length} steps · ${noteCount} notes`}
            </span>
          </div>
        </div>

        {/* Divider */}
        <div style={{ height: '1px', backgroundColor: 'var(--color-border)', margin: 'var(--space-4) 0' }} />

        {/* Row 2 — playback + sliders */}
        <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'flex-end', gap: 'var(--space-8)' }}>
          {/* Play / Stop + Loop */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
            {isPlaying ? (
              <Button variant="danger" size="md" onClick={stopPlayback}
                icon={<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor"><rect x="2" y="2" width="4" height="10" rx="1"/><rect x="8" y="2" width="4" height="10" rx="1"/></svg>}
              >
                Stop
              </Button>
            ) : (
              <Button variant="success" size="md" onClick={handlePlay}
                icon={<svg width="12" height="14" viewBox="0 0 12 14" fill="currentColor"><path d="M0 0l12 7-12 7V0z"/></svg>}
              >
                Play
              </Button>
            )}

            <Button
              variant={loopEnabled ? 'success' : 'ghost'}
              onClick={() => setLoopEnabled((v) => !v)}
            >
              Loop
            </Button>
          </div>

          <span style={{ color: 'var(--color-border-strong)', fontSize: 'var(--font-size-sm)', alignSelf: 'center' }}>|</span>

          {/* BPM slider */}
          <SliderControl
            label="Speed"
            valueLabel={`${speedBpm} bpm`}
            secondaryLabel={`${stepDelayMs} ms`}
            min={30} max={240} step={1}
            value={speedBpm}
            onChange={(v) => setStepDelayMs(bpmToMs(v))}
            style={{ minWidth: 160, flex: '2 1 160px' }}
          />

          {/* Duration slider */}
          <SliderControl
            label="Note Duration"
            valueLabel={`${noteDurationMs} ms`}
            min={20} max={500} step={10}
            value={noteDurationMs}
            onChange={(v) => setNoteDurationMs(v)}
            style={{ minWidth: 140, flex: '2 1 140px' }}
          />

          {/* Measure slider */}
          <SliderControl
            label="Measure"
            valueLabel={`every ${measureEvery}`}
            min={1} max={16} step={1}
            value={measureEvery}
            onChange={(v) => setMeasureEvery(v)}
            style={{ minWidth: 100, flex: '1 1 100px' }}
          />
        </div>
        </div>
      </Card>

      {/* ── Step-sequencer grid ──────────────────────────────────────────── */}
      <Card padding={false}>
        <div style={{ overflowX: 'auto', padding: 'var(--space-4)' }}>
          <table style={{ borderCollapse: 'separate', borderSpacing: 0, width: 'max-content' }}>
            <thead>
              <tr>
                {/* Corner cell */}
                <th style={{
                  position: 'sticky', top: 0, left: 0, zIndex: 30,
                  padding: 'var(--space-2) var(--space-3)',
                  backgroundColor: 'var(--color-bg-surface)',
                  borderBottom: '1px solid var(--color-border)',
                  borderRight:  '1px solid var(--color-border)',
                  fontSize: 'var(--font-size-xs)',
                  color: 'var(--color-text-muted)',
                  fontWeight: 'var(--font-weight-semibold)',
                  textAlign: 'center',
                  userSelect: 'none',
                  whiteSpace: 'nowrap',
                }}>
                  Note / Step
                </th>

                {/* Step headers */}
                {steps.map((_, stepIndex) => {
                  const isCurrent     = stepIndex === currentStep
                  const measureHit    = measureEvery > 0 && (stepIndex + 1) % measureEvery === 0
                  const measureOdd    = measureEvery > 0 && Math.floor(stepIndex / measureEvery) % 2 === 1

                  return (
                    <th
                      key={stepIndex}
                      style={{
                        position: 'sticky', top: 0, zIndex: 20,
                        minWidth: 44, width: 44,
                        padding: 0,
                        backgroundColor: isCurrent
                          ? 'rgba(74,222,128,0.18)'
                          : measureOdd
                            ? 'rgba(192,193,255,0.03)'
                            : 'var(--color-bg-surface)',
                        borderBottom: isCurrent
                          ? '2px solid var(--color-success)'
                          : '1px solid var(--color-border)',
                        borderRight: measureHit
                          ? '2px solid var(--color-border-strong)'
                          : '1px solid var(--color-border)',
                        color: isCurrent ? 'var(--color-success)' : 'var(--color-text-muted)',
                        fontSize: 'var(--font-size-xs)',
                        fontWeight: 'var(--font-weight-semibold)',
                        fontFamily: 'var(--font-family-mono)',
                      }}
                    >
                      {/* Clickable step number — opens context menu */}
                      <div style={{ position: 'relative' }}>
                        <button
                          type="button"
                          aria-label={`Step ${stepIndex + 1} options`}
                          onClick={(e) => {
                            e.stopPropagation()
                            setStepMenuIndex((prev) => (prev === stepIndex ? null : stepIndex))
                          }}
                          style={{
                            display: 'block', width: '100%',
                            padding: 'var(--space-2) var(--space-1)',
                            background: 'transparent', border: 'none',
                            color: 'inherit', cursor: 'pointer',
                            fontFamily: 'inherit', fontSize: 'inherit',
                            fontWeight: 'inherit',
                          }}
                        >
                          {stepIndex + 1}
                        </button>

                        {/* Context menu */}
                        {stepMenuIndex === stepIndex && (
                          <div
                            ref={stepMenuRef}
                            style={{
                              position: 'absolute', top: '100%', right: 0,
                              zIndex: 50, width: 112,
                              backgroundColor: 'var(--color-bg-elevated)',
                              border: '1px solid var(--color-border-strong)',
                              borderRadius: 'var(--radius-md)',
                              boxShadow: 'var(--shadow-lg)',
                              overflow: 'hidden',
                            }}
                          >
                            {[
                              { label: 'Add After',  action: () => { insertStepAt(stepIndex + 1); setStepMenuIndex(null) }, danger: false },
                              { label: 'Add Before', action: () => { insertStepAt(stepIndex);     setStepMenuIndex(null) }, danger: false },
                              { label: 'Delete',     action: () => { deleteStepAt(stepIndex);     setStepMenuIndex(null) }, danger: true  },
                            ].map(({ label, action, danger }) => (
                              <button
                                key={label}
                                type="button"
                                onClick={action}
                                style={{
                                  display: 'block', width: '100%',
                                  padding: 'var(--space-2) var(--space-3)',
                                  background: 'transparent', border: 'none',
                                  textAlign: 'left',
                                  fontSize: 'var(--font-size-xs)',
                                  fontFamily: 'var(--font-family-base)',
                                  color: danger ? 'var(--color-danger)' : 'var(--color-text-primary)',
                                  cursor: 'pointer',
                                }}
                                onMouseEnter={(e) => {
                                  e.currentTarget.style.backgroundColor = 'var(--color-bg-island)'
                                }}
                                onMouseLeave={(e) => {
                                  e.currentTarget.style.backgroundColor = 'transparent'
                                }}
                              >
                                {label}
                              </button>
                            ))}
                          </div>
                        )}
                      </div>
                    </th>
                  )
                })}
              </tr>
            </thead>

            <tbody>
              {Array.from({ length: noteCount }, (_, noteIndex) => (
                <tr key={noteIndex}>
                  {/* Note label */}
                  <th style={{
                    position: 'sticky', left: 0, zIndex: 10,
                    padding: 'var(--space-2) var(--space-3)',
                    backgroundColor: 'var(--color-bg-surface)',
                    borderBottom: '1px solid var(--color-border)',
                    borderRight:  '1px solid var(--color-border)',
                    color: 'var(--color-text-secondary)',
                    fontSize: 'var(--font-size-xs)',
                    fontWeight: 'var(--font-weight-semibold)',
                    fontFamily: 'var(--font-family-mono)',
                    textAlign: 'center',
                    userSelect: 'none',
                    width: 48,
                  }}>
                    <span style={{
                      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                      width: 20, height: 20,
                      borderRadius: '50%',
                      backgroundColor: `${noteDotColor(noteIndex + 1, noteColors)}22`,
                      color: noteDotColor(noteIndex + 1, noteColors),
                    }}>
                      {NOTE_LABELS[noteIndex]}
                    </span>
                  </th>

                  {/* Step cells */}
                  {steps.map((stepValue, stepIndex) => {
                    const enabled       = Boolean(stepValue & (1 << noteIndex))
                    const isCurrent     = stepIndex === currentStep
                    const measureHit    = measureEvery > 0 && (stepIndex + 1) % measureEvery === 0
                    const measureOdd    = measureEvery > 0 && Math.floor(stepIndex / measureEvery) % 2 === 1
                    const dotColor      = noteDotColor(noteIndex + 1, noteColors)
                    const dotGlow       = noteDotGlow(noteIndex + 1, noteColors)

                    return (
                      <td
                        key={`${noteIndex}-${stepIndex}`}
                        style={{
                          width: 44, height: 44,
                          padding: 0,
                          borderBottom: '1px solid var(--color-border)',
                          borderRight: measureHit ? '2px solid var(--color-border-strong)' : '1px solid var(--color-border)',
                          backgroundColor: isCurrent
                            ? 'rgba(74,222,128,0.06)'
                            : measureOdd
                              ? 'rgba(192,193,255,0.015)'
                              : 'transparent',
                        }}
                      >
                        <button
                          type="button"
                          aria-label={`Toggle note ${noteIndex + 1} on step ${stepIndex + 1}`}
                          aria-pressed={enabled}
                          onClick={() => toggleCell(noteIndex, stepIndex)}
                          style={{
                            display: 'flex', alignItems: 'center', justifyContent: 'center',
                            width: '100%', height: '100%',
                            background: 'transparent', border: 'none',
                            cursor: 'pointer', outline: 'none',
                          }}
                        >
                          <span
                            aria-hidden="true"
                            style={{
                              display: 'block',
                              width: '52%', height: '52%',
                              borderRadius: '50%',
                              backgroundColor: dotColor,
                              opacity:   enabled ? 1 : 0,
                              transform: enabled ? 'scale(1)' : 'scale(0.35)',
                              boxShadow: enabled ? `0 0 10px 3px ${dotGlow}` : 'none',
                              transition: 'opacity 120ms ease, transform 120ms ease, box-shadow 160ms ease',
                              flexShrink: 0,
                            }}
                          />
                        </button>
                      </td>
                    )
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </Card>

      {/* ── Output snippets ──────────────────────────────────────────────── */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(320px, 1fr))', gap: 'var(--space-6)' }}>
        <Card title="CSV Notation" subtitle="Paste into melody step field">
          <pre style={{
            margin: 0,
            padding: 'var(--space-3)',
            backgroundColor: 'var(--color-bg-abyss)',
            border: '1px solid var(--color-border)',
            borderRadius: 'var(--radius-md)',
            fontSize: 'var(--font-size-sm)',
            fontFamily: 'var(--font-family-mono)',
            color: 'var(--color-text-primary)',
            overflowX: 'auto',
            whiteSpace: 'pre-wrap',
            wordBreak: 'break-all',
          }}>
            {csvOutput}
          </pre>
        </Card>

        <Card title="PROGMEM Values" subtitle="Copy into firmware melody array">
          <pre style={{
            margin: 0,
            padding: 'var(--space-3)',
            backgroundColor: 'var(--color-bg-abyss)',
            border: '1px solid var(--color-border)',
            borderRadius: 'var(--radius-md)',
            fontSize: 'var(--font-size-sm)',
            fontFamily: 'var(--font-family-mono)',
            color: 'var(--color-text-primary)',
            overflowX: 'auto',
            whiteSpace: 'pre',
          }}>
            {progmemOutput}
          </pre>
        </Card>
      </div>

      {/* ── Deploy modal ─────────────────────────────────────────────────── */}
      <DeployArchetypeModal
        open={deployOpen}
        onClose={() => setDeployOpen(false)}
        onSuccess={handleDeploySuccess}
        mode={deployMode}
        archetype={loadedArchetype}
        stepsStr={steps.map(stepToNotation).join(',')}
      />
    </div>
  )
}

// ─── SliderControl sub-component ─────────────────────────────────────────────
// Inline because it's only used here; not a reusable design system component.

function SliderControl({ label, valueLabel, secondaryLabel, min, max, step, value, onChange, style }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-1)', ...style }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 'var(--space-2)' }}>
        <span style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-secondary)', letterSpacing: 'var(--tracking-wide)', textTransform: 'uppercase' }}>
          {label}
        </span>
        <span style={{ display: 'flex', alignItems: 'baseline', gap: 'var(--space-2)' }}>
          {secondaryLabel && (
            <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', fontFamily: 'var(--font-family-mono)' }}>
              {secondaryLabel}
            </span>
          )}
          <span style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-primary)', fontFamily: 'var(--font-family-mono)' }}>
            {valueLabel}
          </span>
        </span>
      </div>
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(e) => onChange(Number(e.target.value))}
        style={{ width: '100%', accentColor: 'var(--color-primary)', cursor: 'pointer' }}
      />
    </div>
  )
}
