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

const NOTE_LABELS = 'ABCDEFGHIJKLMNOP'

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 = Number.parseInt(part.trim(), 10)
    if (Number.isInteger(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 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(n, palette) {
  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(n, palette) {
  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 normalizeFileUrl(url) {
  if (!url || typeof url !== 'string') return null
  if (url.startsWith('http') || url.startsWith('/api')) return url
  if (url.startsWith('/')) return `/api${url}`
  return `/api/${url}`
}

async function decodeBsmBinary(url) {
  const token = localStorage.getItem('access_token')
  const res = await fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
  if (!res.ok) {
    if (url.startsWith('http')) {
      const retry = await fetch(url)
      if (!retry.ok) throw new Error(`Failed to fetch binary: ${retry.statusText}`)
      const buf2 = await retry.arrayBuffer()
      const view2 = new DataView(buf2)
      const steps2 = []
      for (let i = 0; i + 1 < buf2.byteLength; i += 2) steps2.push(view2.getUint16(i, false))
      return steps2
    }
    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
}

export default function BinaryTableModal({ open, onClose, melody, builtMelody, files, archetypeCsv }) {
  const info = melody?.information || {}
  const [steps, setSteps] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const [noteColors, setNoteColors] = useState([])

  useEffect(() => {
    if (!open) { setSteps([]); setError(''); return }

    const binaryUrlCandidate = builtMelody?.binary_url
      ? `/api${builtMelody.binary_url}`
      : files?.binary_url || melody?.url || null
    const binaryUrl = normalizeFileUrl(binaryUrlCandidate)
    const csv = archetypeCsv || info.archetype_csv || null

    if (binaryUrl) {
      setLoading(true); setError('')
      decodeBsmBinary(binaryUrl)
        .then((decoded) => setSteps(decoded))
        .catch((err) => {
          if (csv) { setSteps(parseStepsString(csv)); return }
          setError(err.message || 'Failed to load melody data.')
        })
        .finally(() => setLoading(false))
      return
    }
    if (csv) { setSteps(parseStepsString(csv)); return }
    setError('No binary or archetype data available for this melody.')
  }, [open]) // eslint-disable-line react-hooks/exhaustive-deps

  useEffect(() => {
    if (!open) return
    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 }
  }, [open])

  const detectedNoteCount = steps.reduce((max, sv) => {
    let h = 0
    for (let b = 15; b >= 0; b--) { if (sv & (1 << b)) { h = b + 1; break } }
    return Math.max(max, h)
  }, 0)
  const gridNoteCount = Math.max(1, Math.min(16, Number(info.totalNotes || 0) || detectedNoteCount || 1))

  return (
    <Modal
      open={open}
      onClose={onClose}
      title="Archetype View"
      size="lg"
      footer={<Button variant="ghost" onClick={onClose}>Close</Button>}
    >
      {loading ? (
        <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-10)' }}>
          <Spinner size="md" />
        </div>
      ) : error ? (
        <div style={{
          padding: 'var(--space-3) var(--space-4)',
          backgroundColor: 'var(--color-danger-bg)',
          border: '1px solid var(--color-danger)',
          borderRadius: 'var(--radius-md)',
          color: 'var(--color-danger)',
          fontSize: 'var(--font-size-sm)',
        }}>
          {error}
        </div>
      ) : steps.length > 0 ? (
        <div style={{ overflowX: 'auto', maxHeight: 520, overflowY: 'auto', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)' }}>
          <table style={{ borderCollapse: 'separate', borderSpacing: 0, fontSize: 'var(--font-size-xs)', minWidth: 'max-content' }}>
            <thead>
              <tr>
                <th style={{ position: 'sticky', top: 0, left: 0, zIndex: 20, padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--color-border)', borderRight: '1px solid var(--color-border)', backgroundColor: 'var(--color-bg-page)', color: 'var(--color-text-muted)', whiteSpace: 'nowrap' }}>
                  Note \ Step
                </th>
                {steps.map((_, si) => (
                  <th key={si} style={{ position: 'sticky', top: 0, zIndex: 10, padding: '6px 8px', textAlign: 'center', minWidth: 40, borderBottom: '1px solid var(--color-border)', borderRight: '1px solid var(--color-border)', backgroundColor: 'var(--color-bg-page)', color: 'var(--color-text-muted)' }}>
                    {si + 1}
                  </th>
                ))}
              </tr>
            </thead>
            <tbody>
              {Array.from({ length: gridNoteCount }, (_, noteIdx) => (
                <tr key={noteIdx}>
                  <th style={{ position: 'sticky', left: 0, zIndex: 1, padding: '6px 8px', textAlign: 'left', borderBottom: '1px solid var(--color-border)', borderRight: '1px solid var(--color-border)', backgroundColor: 'var(--color-bg-page)', color: 'var(--color-text-secondary)' }}>
                    {NOTE_LABELS[noteIdx]}
                  </th>
                  {steps.map((sv, si) => {
                    const enabled = Boolean(sv & (1 << noteIdx))
                    const n = noteIdx + 1
                    return (
                      <td key={`${noteIdx}-${si}`} style={{ width: 40, height: 40, borderBottom: '1px solid var(--color-border)', borderRight: '1px solid var(--color-border)' }}>
                        <span style={{ width: '100%', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                          <span style={{
                            width: '60%', height: '60%', borderRadius: '9999px',
                            backgroundColor: noteDotColor(n, noteColors),
                            opacity: enabled ? 1 : 0,
                            transform: enabled ? 'scale(1)' : 'scale(0.4)',
                            boxShadow: enabled ? `0 0 10px 3px ${noteDotGlow(n, noteColors)}` : 'none',
                            transition: 'opacity 140ms ease, transform 140ms ease',
                            display: 'block',
                          }} />
                        </span>
                      </td>
                    )
                  })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      ) : (
        <div style={{ textAlign: 'center', padding: 'var(--space-10)', color: 'var(--color-text-muted)', fontSize: 'var(--font-size-sm)' }}>
          No data to display.
        </div>
      )}

      {steps.length > 0 && (
        <p style={{ marginTop: 'var(--space-2)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
          {steps.length} steps
        </p>
      )}
    </Modal>
  )
}
