import { useState, useEffect, useRef, useCallback } from 'react'
import Modal from '@/components/ui/Modal'
import Button from '@/components/ui/Button'
import api from '@/lib/api'

// ── Audio helpers ─────────────────────────────────────────────────────────────

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

function playStep(audioCtx, stepValue, beatDurationMs) {
  if (!stepValue || !audioCtx) return
  const now = audioCtx.currentTime
  const duration = beatDurationMs / 1000
  const fadeIn = 0.005; const fadeOut = 0.03
  for (let bit = 0; bit < 16; bit++) {
    if (stepValue & (1 << bit)) {
      const osc = audioCtx.createOscillator()
      const gain = audioCtx.createGain()
      osc.connect(gain); gain.connect(audioCtx.destination)
      osc.type = 'sine'
      osc.frequency.setValueAtTime(bellFrequency(bit + 1), 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.start(now); osc.stop(now + duration)
    }
  }
}

function parseBellNotation(notation) {
  const raw = String(notation || '').trim()
  if (raw === '0' || !raw) return 0
  let value = 0
  for (const part of raw.split('+')) {
    const n = parseInt(part.trim(), 10)
    if (!isNaN(n) && n >= 1 && n <= 16) value |= 1 << (n - 1)
  }
  return value
}

function parseStepsString(stepsStr) {
  if (!stepsStr || !String(stepsStr).trim()) return []
  return String(stepsStr).trim().split(',').map((s) => parseBellNotation(s))
}

function getActiveBells(stepValue) {
  const bells = []
  for (let bit = 0; bit < 16; bit++) { if (stepValue & (1 << bit)) bells.push(bit + 1) }
  return bells
}

async function decodeBsmBinary(url) {
  const token = localStorage.getItem('access_token')
  const res = await fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
  if (!res.ok) throw new Error(`Failed to fetch binary: ${res.statusText}`)
  const buf = await res.arrayBuffer()
  const view = new DataView(buf); const steps = []
  for (let i = 0; i + 1 < buf.byteLength; i += 2) steps.push(view.getUint16(i, false))
  return steps
}

function calcMin(normal, max) {
  if (!normal || !max || max <= 0) return null
  return Math.round((normal * normal) / max)
}

// Slider is inverted: right = fast (low ms), left = slow (high ms)
const DELAY_MIN = 50; const DELAY_MAX = 3000
function delayToSlider(ms) { return DELAY_MIN + DELAY_MAX - ms }
function sliderToDelay(val) { return DELAY_MIN + DELAY_MAX - val }

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

export default function SpeedCalculatorModal({ open, onClose, melody, builtMelody, archetypeCsv, onSaved }) {
  const info = melody?.information || {}

  const [stepsInput, setStepsInput] = useState('')
  const [steps, setSteps] = useState([])
  const [loadingBinary, setLoadingBinary] = useState(false)
  const [binaryLoadError, setBinaryLoadError] = useState('')

  const audioCtxRef = useRef(null)
  const playbackRef = useRef(null)
  const flashTimerRef = useRef(null)
  const stepsRef = useRef([])
  const stepDelayRef = useRef(500)
  const effectiveBeatRef = useRef(100)
  const loopRef = useRef(true)

  const [playing, setPlaying] = useState(false)
  const [paused, setPaused] = useState(false)
  const [loop, setLoop] = useState(true)
  const [currentStep, setCurrentStep] = useState(-1)
  const [activeBells, setActiveBells] = useState(new Set())
  const [stepDelay, setStepDelay] = useState(500)
  const [beatDuration, setBeatDuration] = useState(100)

  const effectiveBeat = Math.min(beatDuration, Math.max(20, stepDelay - 20))

  const [capturedMax, setCapturedMax] = useState(null)
  const [capturedNormal, setCapturedNormal] = useState(null)
  const derivedMin = calcMin(capturedNormal, capturedMax)

  const [saving, setSaving] = useState(false)
  const [saveError, setSaveError] = useState('')
  const [saveSuccess, setSaveSuccess] = useState(false)

  const maxWarning = capturedMax !== null && capturedMax < 100
  const orderWarning = capturedMax !== null && capturedNormal !== null && capturedNormal < capturedMax

  useEffect(() => { stepsRef.current = steps }, [steps])
  useEffect(() => { stepDelayRef.current = stepDelay }, [stepDelay])
  useEffect(() => { effectiveBeatRef.current = effectiveBeat }, [effectiveBeat])
  useEffect(() => { loopRef.current = loop }, [loop])

  useEffect(() => {
    if (!open) return
    const max = info.maxSpeed > 0 ? info.maxSpeed : null
    const min = info.minSpeed > 0 ? info.minSpeed : null
    const normal = (max !== null && min !== null) ? Math.round(Math.sqrt(min * max)) : null
    setCapturedMax(max); setCapturedNormal(normal)
    setBinaryLoadError(''); setCurrentStep(-1); setActiveBells(new Set())
    setPlaying(false); setPaused(false); setSaveError(''); setSaveSuccess(false)
    const csv = archetypeCsv || info.archetype_csv || null
    if (csv) {
      const parsed = parseStepsString(csv)
      setStepsInput(csv); setSteps(parsed); stepsRef.current = parsed
    } else {
      setStepsInput(''); setSteps([])
    }
  }, [open]) // eslint-disable-line react-hooks/exhaustive-deps

  const stopPlayback = useCallback(() => {
    if (playbackRef.current) { clearTimeout(playbackRef.current.timer); playbackRef.current = null }
    if (flashTimerRef.current) { clearTimeout(flashTimerRef.current); flashTimerRef.current = null }
    setPlaying(false); setPaused(false); setCurrentStep(-1); setActiveBells(new Set())
  }, [])

  useEffect(() => { if (!open) stopPlayback() }, [open, stopPlayback])

  const ensureAudioCtx = () => {
    if (!audioCtxRef.current || audioCtxRef.current.state === 'closed')
      audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)()
    if (audioCtxRef.current.state === 'suspended') audioCtxRef.current.resume()
    return audioCtxRef.current
  }

  const scheduleStep = useCallback((stepIndex) => {
    const currentSteps = stepsRef.current
    if (!currentSteps.length) return
    const playFrom = stepIndex % currentSteps.length
    if (!loopRef.current && playFrom === 0 && stepIndex > 0) {
      setPlaying(false); setPaused(false); setCurrentStep(-1); playbackRef.current = null; return
    }
    const ctx = ensureAudioCtx()
    const stepValue = currentSteps[playFrom]
    setCurrentStep(playFrom)
    const bellsNow = new Set()
    getActiveBells(stepValue).forEach((b) => bellsNow.add(b))
    setActiveBells(bellsNow)
    if (flashTimerRef.current) clearTimeout(flashTimerRef.current)
    flashTimerRef.current = setTimeout(() => { setActiveBells(new Set()); flashTimerRef.current = null }, effectiveBeatRef.current)
    playStep(ctx, stepValue, effectiveBeatRef.current)
    const timer = setTimeout(() => {
      const next = playFrom + 1
      if (next >= stepsRef.current.length) {
        if (loopRef.current) scheduleStep(0)
        else { setPlaying(false); setPaused(false); setCurrentStep(-1); playbackRef.current = null }
      } else scheduleStep(next)
    }, stepDelayRef.current)
    playbackRef.current = { timer, stepIndex: playFrom }
  }, [])

  const handlePlay = () => {
    if (!stepsRef.current.length) return
    if (paused && playbackRef.current) { setPaused(false); setPlaying(true); scheduleStep(playbackRef.current.stepIndex); return }
    setPlaying(true); setPaused(false); scheduleStep(0)
  }

  const handlePause = () => {
    if (playbackRef.current) clearTimeout(playbackRef.current.timer)
    setPaused(true); setPlaying(false)
  }

  const handleStepsParse = () => {
    const parsed = parseStepsString(stepsInput)
    setSteps(parsed); stepsRef.current = parsed; stopPlayback(); setBinaryLoadError('')
  }

  const handleLoadFromBinary = async () => {
    const binaryUrl = builtMelody?.binary_url ? `/api${builtMelody.binary_url}` : melody?.url || null
    if (!binaryUrl) return
    setLoadingBinary(true); setBinaryLoadError('')
    try {
      const decoded = await decodeBsmBinary(binaryUrl)
      setSteps(decoded); stepsRef.current = decoded; stopPlayback()
      setStepsInput(decoded.map((v) => !v ? '0' : getActiveBells(v).join('+')).join(','))
    } catch (err) {
      setBinaryLoadError(err.message)
    } finally {
      setLoadingBinary(false)
    }
  }

  const handleSave = async () => {
    if (!capturedMax || !capturedNormal || !derivedMin) return
    stopPlayback(); setSaving(true); setSaveError(''); setSaveSuccess(false)
    try {
      const body = {
        information: { ...info, minSpeed: derivedMin, maxSpeed: capturedMax },
        default_settings: melody.default_settings,
        type: melody.type,
        uid: melody.uid,
        pid: melody.pid,
      }
      if (melody.url) body.url = melody.url
      await api.put(`/melodies/${melody.id}`, body)
      setSaveSuccess(true)
      setTimeout(() => onSaved?.(), 800)
    } catch (err) {
      setSaveError(err.message)
    } finally {
      setSaving(false)
    }
  }

  const totalSteps = steps.length
  const allBellsUsed = steps.reduce((set, v) => { getActiveBells(v).forEach((b) => set.add(b)); return set }, new Set())
  const maxBell = allBellsUsed.size > 0 ? Math.max(...Array.from(allBellsUsed)) : 0

  return (
    <Modal
      open={open}
      onClose={() => { stopPlayback(); onClose() }}
      title="Speed Calculator"
      size="lg"
      footer={
        <div style={{ display: 'flex', gap: 'var(--space-2)', justifyContent: 'flex-end' }}>
          <Button variant="ghost" onClick={() => { stopPlayback(); onClose() }}>Close</Button>
          <Button
            variant="primary"
            disabled={!capturedMax || !capturedNormal || !derivedMin || saving || orderWarning}
            loading={saving}
            onClick={handleSave}
          >
            {saving ? 'Saving…' : 'Save to Melody'}
          </Button>
        </div>
      }
    >
      <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-4)', marginTop: 'calc(var(--space-1) * -1)' }}>
        Play the melody at different speeds to find the right MIN / MAX.
      </p>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-6)' }}>

        {/* LEFT: Steps + playback */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>

          {/* Steps input */}
          <div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-1)' }}>
              <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)' }}>Melody Steps</span>
              {(builtMelody?.binary_url || melody?.url) && (
                <button
                  onClick={handleLoadFromBinary}
                  disabled={loadingBinary}
                  style={{ fontSize: 'var(--font-size-xs)', padding: '3px 8px', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', color: 'var(--color-text-secondary)', cursor: loadingBinary ? 'not-allowed' : 'pointer', opacity: loadingBinary ? 0.5 : 1 }}
                >
                  {loadingBinary ? 'Loading…' : 'Load from Binary'}
                </button>
              )}
            </div>
            {binaryLoadError && (
              <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-danger)', marginBottom: 'var(--space-1)' }}>{binaryLoadError}</p>
            )}
            <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
              <textarea
                value={stepsInput}
                onChange={(e) => setStepsInput(e.target.value)}
                rows={3}
                placeholder="e.g. 1,2,2+1,1,2,3+1,0,3..."
                style={{ flex: 1, padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border)', backgroundColor: 'var(--color-bg-elevated)', color: 'var(--color-text-primary)', fontSize: 'var(--font-size-sm)', fontFamily: 'var(--font-family-mono)', resize: 'none' }}
              />
              <Button variant="primary" size="sm" onClick={handleStepsParse} style={{ alignSelf: 'flex-start' }}>Load</Button>
            </div>
            {totalSteps > 0 && (
              <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-success, #22c55e)', marginTop: 'var(--space-1)' }}>
                {totalSteps} steps · {allBellsUsed.size} bell{allBellsUsed.size !== 1 ? 's' : ''}
              </p>
            )}
          </div>

          {/* Bell visualizer */}
          {totalSteps > 0 && maxBell > 0 && (
            <div>
              <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-2)' }}>
                Notes{currentStep >= 0 ? ` · Step ${currentStep + 1}/${totalSteps}` : ''}
              </p>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-1)' }}>
                {Array.from({ length: maxBell }, (_, i) => i + 1).map((b) => {
                  const isActive = activeBells.has(b)
                  const isUsed = allBellsUsed.has(b)
                  return (
                    <div key={b} style={{
                      width: 28, height: 28, borderRadius: '9999px',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-bold)',
                      backgroundColor: isActive ? 'var(--color-primary)' : isUsed ? 'var(--color-bg-elevated)' : 'var(--color-bg-page)',
                      color: isActive ? '#fff' : isUsed ? 'var(--color-text-secondary)' : 'var(--color-border)',
                      border: `2px solid ${isActive ? 'var(--color-primary)' : 'var(--color-border)'}`,
                      transform: isActive ? 'scale(1.15)' : 'scale(1)',
                      transition: 'all 80ms ease',
                    }}>
                      {b}
                    </div>
                  )
                })}
              </div>
            </div>
          )}

          {/* Playback controls */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
            {!playing ? (
              <Button variant="primary" size="sm" disabled={!totalSteps} onClick={handlePlay}>
                {paused ? 'Resume' : 'Play'}
              </Button>
            ) : (
              <Button variant="secondary" size="sm" onClick={handlePause}>Pause</Button>
            )}
            <Button variant="ghost" size="sm" disabled={!playing && !paused} onClick={stopPlayback}>Stop</Button>
            <label style={{ display: 'inline-flex', alignItems: 'center', gap: 'var(--space-2)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)', cursor: 'pointer' }}>
              <input type="checkbox" checked={loop} onChange={(e) => { setLoop(e.target.checked); loopRef.current = e.target.checked }} style={{ width: 14, height: 14 }} />
              Loop
            </label>
          </div>

          {/* Step Delay slider (inverted) */}
          <div>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--space-1)' }}>
              <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)' }}>Step Delay</span>
              <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-bold)', color: 'var(--color-primary)' }}>{stepDelay} ms</span>
            </div>
            <input type="range" min={DELAY_MIN} max={DELAY_MAX} step="10"
              value={delayToSlider(stepDelay)}
              onChange={(e) => setStepDelay(sliderToDelay(Number(e.target.value)))}
              style={{ width: '100%', accentColor: 'var(--color-primary)', cursor: 'pointer' }}
            />
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginTop: 2 }}>
              <span>Slow (3000ms)</span><span>Fast (50ms)</span>
            </div>
          </div>

          {/* Tone Length slider */}
          <div>
            <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--space-1)' }}>
              <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)' }}>Tone Length</span>
              <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
                {effectiveBeat} ms
                {effectiveBeat < beatDuration && <span style={{ color: 'var(--color-warning, #f59e0b)' }}> (capped)</span>}
              </span>
            </div>
            <input type="range" min="20" max="500" step="5"
              value={beatDuration}
              onChange={(e) => setBeatDuration(Number(e.target.value))}
              style={{ width: '100%', accentColor: 'var(--color-primary)', cursor: 'pointer' }}
            />
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginTop: 2 }}>
              <span>20ms</span><span>500ms</span>
            </div>
          </div>
        </div>

        {/* RIGHT: Speed Capture */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)', padding: 'var(--space-4)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-bg-elevated)' }}>
          <div>
            <h3 style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>Speed Capture</h3>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
              Find the fastest safe speed (MAX), then a natural speed (Normal). MIN is auto-derived so 50% slider = Normal.
            </p>
          </div>

          {[
            { label: 'MAX — fastest safe', value: capturedMax, onCapture: () => setCapturedMax(stepDelay), onSet: () => capturedMax && setStepDelay(capturedMax), accent: true },
            { label: 'Normal — at 50% speed', value: capturedNormal, onCapture: () => setCapturedNormal(stepDelay), onSet: () => capturedNormal && setStepDelay(capturedNormal), accent: true },
          ].map(({ label, value, onCapture, onSet, accent }) => (
            <div key={label} style={{ padding: 'var(--space-3)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-bg-island, var(--color-bg-page))' }}>
              <span style={{ display: 'block', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-2)' }}>
                {label}
              </span>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                <span style={{ fontSize: 22, fontWeight: 'var(--font-weight-bold)', color: value !== null ? 'var(--color-primary)' : 'var(--color-text-muted)' }}>
                  {value !== null ? `${value} ms` : '—'}
                </span>
                <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
                  <button disabled={value === null} onClick={onSet}
                    style={{ fontSize: 'var(--font-size-xs)', padding: '3px 8px', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', color: 'var(--color-text-muted)', cursor: value === null ? 'not-allowed' : 'pointer', opacity: value === null ? 0.4 : 1 }}
                    title="Set slider to this value"
                  >
                    Set ↑
                  </button>
                  <Button variant="primary" size="sm" onClick={onCapture}>Capture</Button>
                </div>
              </div>
            </div>
          ))}

          <div style={{ padding: 'var(--space-3)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-bg-island, var(--color-bg-page))' }}>
            <span style={{ display: 'block', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: 'var(--color-text-muted)', marginBottom: 4 }}>
              MIN — auto-calculated
              {derivedMin !== null && <span style={{ fontWeight: 'normal' }}> (normal² / max)</span>}
            </span>
            <span style={{ fontSize: 22, fontWeight: 'var(--font-weight-bold)', color: derivedMin !== null ? 'var(--color-text-secondary)' : 'var(--color-text-muted)' }}>
              {derivedMin !== null ? `${derivedMin} ms` : '—'}
            </span>
          </div>

          {maxWarning && (
            <div style={{ fontSize: 'var(--font-size-xs)', padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-danger-bg)', color: 'var(--color-danger)' }}>
              Warning: MAX of {capturedMax}ms is very fast (&lt;100ms). This could damage the device.
            </div>
          )}
          {orderWarning && (
            <div style={{ fontSize: 'var(--font-size-xs)', padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-danger-bg)', color: 'var(--color-danger)' }}>
              Warning: Normal ({capturedNormal}ms) is faster than MAX ({capturedMax}ms). Normal should be slower (higher ms).
            </div>
          )}
          {saveError && (
            <div style={{ fontSize: 'var(--font-size-xs)', padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-danger-bg)', color: 'var(--color-danger)' }}>
              {saveError}
            </div>
          )}
          {saveSuccess && (
            <div style={{ fontSize: 'var(--font-size-xs)', padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-success-bg, #dcfce7)', color: 'var(--color-success, #16a34a)' }}>
              Speeds saved to melody!
            </div>
          )}
        </div>
      </div>
    </Modal>
  )
}
