// frontend/src/pages/melodies/MelodyForm.jsx

import { useState, useEffect, useRef } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import PageHeader from '@/components/ui/PageHeader'
import Card from '@/components/ui/Card'
import Button from '@/components/ui/Button'
import FormField from '@/components/ui/FormField'
import Spinner from '@/components/ui/Spinner'
import { useAuth } from '@/hooks/useAuth'
import api from '@/lib/api'
import {
  getLocalizedValue,
  getLanguageName,
  normalizeColor,
  formatDuration,
  parseLocalizedString,
  serializeLocalizedString,
} from '@/lib/melodyUtils'
import TranslationModal    from '@/modals/bellcloud/melodies/TranslationModal'
import SelectArchetypeModal from '@/modals/bellcloud/melodies/SelectArchetypeModal'
import BuildOnTheFlyModal   from '@/modals/bellcloud/melodies/BuildOnTheFlyModal'
import SpeedCalculatorModal from '@/modals/bellcloud/melodies/SpeedCalculatorModal'
import PlaybackModal        from '@/modals/bellcloud/melodies/PlaybackModal'
import BinaryTableModal     from '@/modals/bellcloud/melodies/BinaryTableModal'

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

const MELODY_TYPES = ['orthodox', 'catholic', 'all']
const MELODY_TONES = ['normal', 'festive', 'cheerful', 'lamentation']

const defaultInfo = {
  name: '', description: '', melodyTone: 'normal', customTags: [],
  minSpeed: 0, maxSpeed: 0, totalNotes: 1, totalActiveBells: 0,
  steps: 0, color: '', isTrueRing: false, previewURL: '',
}

const defaultSettings = {
  speed: 50, duration: 0, totalRunDuration: 0, pauseDuration: 0,
  infiniteLoop: false, echoRing: [], noteAssignments: [],
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

function formatBpm(ms) {
  const v = Number(ms)
  if (!v || v <= 0) return null
  return Math.round(60000 / v)
}

function mapPercentageToStepDelay(percent, minSpeed, maxSpeed) {
  const p = Math.max(0, Math.min(100, Number(percent || 0)))
  const t = p / 100
  const a = Number(minSpeed); const b = Number(maxSpeed)
  if (a <= 0 || b <= 0) return Math.round(a + (b - a) * t)
  return Math.round(a * Math.pow(b / a, t))
}

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}`
}

function computeActiveBells(assignments) {
  return new Set((assignments || []).filter((v) => v > 0)).size
}

// ─── Section header helper ────────────────────────────────────────────────────

function SectionLabel({ children }) {
  return (
    <div style={{
      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)',
    }}>
      {children}
    </div>
  )
}

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

export default function MelodyForm() {
  const { id } = useParams()
  const isEdit = Boolean(id)
  const navigate = useNavigate()
  const { user } = useAuth()

  const [information,    setInformation]    = useState({ ...defaultInfo })
  const [settings,       setSettings]       = useState({ ...defaultSettings })
  const [type,           setType]           = useState('all')
  const [url,            setUrl]            = useState('')
  const [pid,            setPid]            = useState('')
  const [melodyStatus,   setMelodyStatus]   = useState('draft')
  const [previewFile,    setPreviewFile]    = useState(null)
  const [existingFiles,  setExistingFiles]  = useState({})
  const [uploading,      setUploading]      = useState(false)
  const [loading,        setLoading]        = useState(false)
  const [saving,         setSaving]         = useState(false)
  const [error,          setError]          = useState('')
  const [tagInput,       setTagInput]       = useState('')
  const [melodySettings, setMelodySettings] = useState(null)
  const [editLang,       setEditLang]       = useState('en')
  const [adminNotes,     setAdminNotes]     = useState([])
  const [newNote,        setNewNote]        = useState('')
  const [savedMelodyId,  setSavedMelodyId]  = useState(null)
  const [builtMelody,    setBuiltMelody]    = useState(null)
  const [assignedBinaryPid, setAssignedBinaryPid] = useState(null)

  // Modal state
  const [translationModal, setTranslationModal] = useState({ open: false, field: '', fieldKey: '', multiline: false })
  const [showSelectBuilt,  setShowSelectBuilt]  = useState(false)
  const [showBuildOnTheFly,setShowBuildOnTheFly]= useState(false)
  const [showSpeedCalc,    setShowSpeedCalc]    = useState(false)
  const [showPlayback,     setShowPlayback]     = useState(false)
  const [showBinaryView,   setShowBinaryView]   = useState(false)

  const previewInputRef = useRef(null)

  // ── Lifecycle ─────────────────────────────────────────────────────────────

  useEffect(() => {
    api.get('/settings/melody').then((ms) => {
      setMelodySettings(ms)
      setEditLang(ms.primary_language || 'en')
    }).catch(() => {})
  }, [])

  useEffect(() => {
    if (isEdit) loadMelody()
  }, [id])

  // Keep noteAssignments array in sync with totalNotes
  useEffect(() => {
    const count = information.totalNotes || 1
    setSettings((prev) => {
      const current = [...(prev.noteAssignments || [])]
      if (current.length < count) {
        while (current.length < count) current.push(0)
      } else if (current.length > count) {
        current.length = count
      }
      return { ...prev, noteAssignments: current }
    })
  }, [information.totalNotes])

  const loadMelody = async () => {
    setLoading(true)
    try {
      const [melody, files] = await Promise.all([
        api.get(`/melodies/${id}`),
        api.get(`/melodies/${id}/files`),
      ])
      setInformation({ ...defaultInfo, ...melody.information })
      setSettings({ ...defaultSettings, ...melody.default_settings })
      setType(melody.type || 'all')
      setUrl(melody.url || '')
      setPid(melody.pid || '')
      setMelodyStatus(melody.status || 'published')
      setExistingFiles(files)
      setAdminNotes(melody.metadata?.adminNotes || [])
      try {
        const bm = await api.get(`/builder/melodies/for-melody/${id}`)
        setBuiltMelody(bm || null)
        setAssignedBinaryPid(bm?.pid || null)
      } catch { setBuiltMelody(null) }
    } catch (err) {
      setError(err.message || 'Failed to load melody.')
    } finally {
      setLoading(false)
    }
  }

  // ── Field helpers ─────────────────────────────────────────────────────────

  const updateInfo     = (field, value) => setInformation((prev) => ({ ...prev, [field]: value }))
  const updateSettings = (field, value) => setSettings((prev) => ({ ...prev, [field]: value }))

  const updateLocalizedField = (fieldKey, text) => {
    const dict = parseLocalizedString(information[fieldKey])
    dict[editLang] = text
    updateInfo(fieldKey, serializeLocalizedString(dict))
  }

  const addTag = () => {
    const tag = tagInput.trim()
    if (tag && !information.customTags.includes(tag))
      updateInfo('customTags', [...information.customTags, tag])
    setTagInput('')
  }

  const removeTag = (tag) =>
    updateInfo('customTags', information.customTags.filter((t) => t !== tag))

  const parseIntList = (str) => {
    if (!str.trim()) return []
    return str.split(',').map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n))
  }

  // ── File helpers ──────────────────────────────────────────────────────────

  const getEffectiveBinary = () => {
    const effectiveUrl = normalizeFileUrl(
      (builtMelody?.binary_url ? `/api${builtMelody.binary_url}` : null) ||
      url || existingFiles.binary_url || null
    )
    const fixedName = builtMelody?.pid || assignedBinaryPid || pid || null
    const effectiveName = fixedName ? `${fixedName}.bsm` : (effectiveUrl ? 'binary.bsm' : null)
    return { effectiveUrl, effectiveName }
  }

  const uploadFiles = async (melodyId) => {
    if (previewFile) {
      setUploading(true)
      await api.upload(`/melodies/${melodyId}/upload/preview`, previewFile)
      setUploading(false)
    }
  }

  // ── Ensure melody exists before opening binary modals ─────────────────────

  const ensureSaved = async () => {
    if (isEdit || savedMelodyId) return isEdit ? id : savedMelodyId
    setSaving(true)
    setError('')
    try {
      const now = new Date().toISOString()
      const userName = user?.name || 'Unknown'
      const body = buildBody({ dateCreated: now, dateEdited: now, createdBy: userName, lastEditedBy: userName, adminNotes })
      const created = await api.post('/melodies?publish=false', body)
      setSavedMelodyId(created.id)
      return created.id
    } catch (err) {
      setError(err.message)
      return null
    } finally {
      setSaving(false)
    }
  }

  // ── Build request body ────────────────────────────────────────────────────

  const buildBody = (metadata) => {
    const { notes: _notes, ...infoWithoutNotes } = information
    const totalActiveBells = computeActiveBells(settings.noteAssignments)
    const body = {
      information: { ...infoWithoutNotes, totalActiveBells },
      default_settings: settings,
      type, pid,
      metadata,
    }
    if (url) body.url = url
    return body
  }

  // ── Save / publish ────────────────────────────────────────────────────────

  const handleSave = async (publish) => {
    setSaving(true)
    setError('')
    try {
      const now = new Date().toISOString()
      const userName = user?.name || 'Unknown'
      let melodyId = id

      if (isEdit) {
        await api.put(`/melodies/${id}`, buildBody({ dateEdited: now, lastEditedBy: userName, adminNotes }))
      } else if (savedMelodyId) {
        melodyId = savedMelodyId
        await api.put(`/melodies/${savedMelodyId}`, buildBody({ dateEdited: now, lastEditedBy: userName, adminNotes }))
        if (publish) await api.post(`/melodies/${savedMelodyId}/publish`)
      } else {
        const body = buildBody({ dateCreated: now, dateEdited: now, createdBy: userName, lastEditedBy: userName, adminNotes })
        const created = await api.post(`/melodies?publish=${publish}`, body)
        melodyId = created.id
        setSavedMelodyId(melodyId)
      }
      await uploadFiles(melodyId)
      navigate(`/melodies/${melodyId}`)
    } catch (err) {
      setError(err.message || 'Save failed.')
      setUploading(false)
    } finally {
      setSaving(false)
    }
  }

  const handlePublishAction = async () => {
    setSaving(true)
    setError('')
    try {
      const now = new Date().toISOString()
      const userName = user?.name || 'Unknown'
      await api.put(`/melodies/${id}`, buildBody({ dateEdited: now, lastEditedBy: userName, adminNotes }))
      await uploadFiles(id)
      await api.post(`/melodies/${id}/publish`)
      navigate(`/melodies/${id}`)
    } catch (err) {
      setError(err.message)
      setUploading(false)
    } finally {
      setSaving(false)
    }
  }

  const handleUnpublishAction = async () => {
    setSaving(true)
    setError('')
    try {
      await api.post(`/melodies/${id}/unpublish`)
      navigate(`/melodies/${id}`)
    } catch (err) {
      setError(err.message)
    } finally {
      setSaving(false)
    }
  }

  // ── Derived ───────────────────────────────────────────────────────────────

  const languages      = melodySettings?.available_languages || ['en']
  const durationValues = melodySettings?.duration_values || [0]
  const quickColors    = melodySettings?.quick_colors || []
  const durationIndex  = durationValues.indexOf(settings.duration)
  const currentDurIdx  = durationIndex >= 0 ? durationIndex : 0
  const speedMs        = mapPercentageToStepDelay(settings.speed, information.minSpeed, information.maxSpeed)
  const speedBpm       = formatBpm(speedMs)
  const minBpm         = formatBpm(information.minSpeed)
  const maxBpm         = formatBpm(information.maxSpeed)
  const { effectiveUrl: binaryUrl, effectiveName: binaryName } = getEffectiveBinary()
  const missingArchetype = Boolean(pid) && !builtMelody?.id
  const isBusy = saving || uploading

  // ── Loading state ─────────────────────────────────────────────────────────

  if (loading) {
    return (
      <div className="page-wrapper">
        <PageHeader title={isEdit ? 'Edit Melody' : 'New Melody'} />
        <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-16)' }}>
          <Spinner size="lg" />
        </div>
      </div>
    )
  }

  // ─── Render ───────────────────────────────────────────────────────────────

  return (
    <div className="page-wrapper">

      {/* ── Header ──────────────────────────────────────────────────────── */}
      <PageHeader title={isEdit ? 'Edit Melody' : 'New Melody'}>
        {isEdit ? (
          <>
            <Button variant="secondary" onClick={() => setShowPlayback(true)}>Playback</Button>
            <Button variant="secondary" onClick={() => setShowSpeedCalc(true)}>Speed Calc</Button>
            <Button variant="secondary" onClick={() => navigate(`/melodies/${id}`)}>Cancel</Button>
            {melodyStatus === 'draft' ? (
              <>
                <Button variant="secondary" disabled={isBusy} loading={saving} onClick={() => handleSave(false)}>
                  Save Draft
                </Button>
                <Button variant="primary" disabled={isBusy} loading={uploading || saving} onClick={handlePublishAction}>
                  Publish
                </Button>
              </>
            ) : (
              <>
                <Button variant="secondary" disabled={isBusy} loading={saving} onClick={() => handleSave(false)}>
                  {uploading ? 'Uploading…' : 'Update'}
                </Button>
                <Button variant="danger" disabled={isBusy} onClick={handleUnpublishAction}>
                  Unpublish
                </Button>
              </>
            )}
          </>
        ) : (
          <>
            <Button variant="ghost" onClick={async () => {
              if (savedMelodyId) {
                try { await api.delete(`/melodies/${savedMelodyId}`) } catch { /* best-effort */ }
              }
              navigate('/melodies')
            }}>
              Cancel
            </Button>
            <Button variant="secondary" disabled={isBusy} loading={saving} onClick={() => handleSave(false)}>
              Save Draft
            </Button>
            <Button variant="primary" disabled={isBusy} loading={isBusy} onClick={() => handleSave(true)}>
              Publish Melody
            </Button>
          </>
        )}
      </PageHeader>

      {/* ── Error banner ────────────────────────────────────────────────── */}
      {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>
      )}

      {/* ── Form body ───────────────────────────────────────────────────── */}
      <form id="melody-form" onSubmit={(e) => { e.preventDefault(); handleSave(false) }}>
        <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">

          {/* ── Left column ─────────────────────────────────────────────── */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)' }}>

            {/* Melody Information */}
            <Card title="Melody Information">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>

                {/* Name — localized */}
                <div>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-1)' }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
                      <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-secondary)' }}>
                        Name <span style={{ color: 'var(--color-danger)' }}>*</span>
                      </span>
                      <button
                        type="button"
                        title="Edit translations"
                        onClick={() => setTranslationModal({ open: true, field: 'Name', fieldKey: 'name', multiline: false })}
                        style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center' }}
                      >
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                          <circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/>
                          <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
                        </svg>
                      </button>
                    </div>
                    {languages.length > 1 && (
                      <select
                        value={editLang}
                        onChange={(e) => setEditLang(e.target.value)}
                        style={{ fontSize: 'var(--font-size-xs)', padding: '2px var(--space-2)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-sm)', color: 'var(--color-text-primary)', cursor: 'pointer' }}
                      >
                        {languages.map((l) => <option key={l} value={l}>{getLanguageName(l)}</option>)}
                      </select>
                    )}
                  </div>
                  <FormField
                    label=""
                    name="melody-name"
                    type="text"
                    required
                    value={getLocalizedValue(information.name, editLang, '')}
                    onChange={(e) => updateLocalizedField('name', e.target.value)}
                    placeholder="Melody name…"
                  />
                </div>

                {/* Description — localized */}
                <div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-1)' }}>
                    <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-secondary)' }}>
                      Description
                    </span>
                    <button
                      type="button"
                      title="Edit translations"
                      onClick={() => setTranslationModal({ open: true, field: 'Description', fieldKey: 'description', multiline: true })}
                      style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center' }}
                    >
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                        <circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/>
                        <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
                      </svg>
                    </button>
                  </div>
                  <FormField
                    label=""
                    name="melody-description"
                    type="textarea"
                    rows={3}
                    value={getLocalizedValue(information.description, editLang, '')}
                    onChange={(e) => updateLocalizedField('description', e.target.value)}
                    placeholder="Optional description…"
                  />
                </div>

                {/* Tone + Type */}
                <div className="grid grid-cols-2 gap-4">
                  <FormField label="Melody Tone" name="tone" type="select"
                    value={information.melodyTone} onChange={(e) => updateInfo('melodyTone', e.target.value)}>
                    {MELODY_TONES.map((t) => <option key={t} value={t}>{t.charAt(0).toUpperCase() + t.slice(1)}</option>)}
                  </FormField>
                  <FormField label="Type" name="type" type="select"
                    value={type} onChange={(e) => setType(e.target.value)}>
                    {MELODY_TYPES.map((t) => <option key={t} value={t}>{t.charAt(0).toUpperCase() + t.slice(1)}</option>)}
                  </FormField>
                </div>

                {/* Notes + Steps */}
                <div className="grid grid-cols-2 gap-4">
                  <FormField label="Total Archetype Notes" name="totalNotes" type="number"
                    value={information.totalNotes}
                    onChange={(e) => updateInfo('totalNotes', Math.max(1, Math.min(16, parseInt(e.target.value, 10) || 1)))}
                    inputProps={{ min: 1, max: 16 }}
                  />
                  <FormField label="Steps" name="steps" type="number"
                    value={information.steps}
                    onChange={(e) => updateInfo('steps', parseInt(e.target.value, 10) || 0)}
                    inputProps={{ min: 0 }}
                  />
                </div>

                {/* Min + Max speed */}
                <div className="grid grid-cols-2 gap-4">
                  <FormField label="Min Speed (ms)" name="minSpeed" type="number"
                    value={information.minSpeed}
                    onChange={(e) => updateInfo('minSpeed', parseInt(e.target.value, 10) || 0)}
                    hint={information.minSpeed > 0 ? `${minBpm} bpm` : undefined}
                    inputProps={{ min: 0 }}
                  />
                  <FormField label="Max Speed (ms)" name="maxSpeed" type="number"
                    value={information.maxSpeed}
                    onChange={(e) => updateInfo('maxSpeed', parseInt(e.target.value, 10) || 0)}
                    hint={information.maxSpeed > 0 ? `${maxBpm} bpm` : undefined}
                    inputProps={{ min: 0 }}
                  />
                </div>

                {/* Color */}
                <div>
                  <SectionLabel>Color</SectionLabel>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-2)' }}>
                    <span style={{ width: 32, height: 32, borderRadius: 'var(--radius-sm)', flexShrink: 0, border: '1px solid var(--color-border)', backgroundColor: information.color ? normalizeColor(information.color) : 'var(--color-bg-island)', display: 'inline-block' }} />
                    <FormField label="" name="color" type="text"
                      value={information.color}
                      onChange={(e) => updateInfo('color', e.target.value)}
                      placeholder="#FF5733 or 0xFF5733"
                      inputProps={{ style: { fontFamily: 'var(--font-family-mono)' } }}
                    />
                  </div>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-2)', alignItems: 'center' }}>
                    {quickColors.map((color) => (
                      <button key={color} type="button"
                        onClick={() => updateInfo('color', color)}
                        style={{
                          width: 28, height: 28, borderRadius: 'var(--radius-sm)',
                          backgroundColor: normalizeColor(color),
                          border: `2px solid ${information.color === color ? 'var(--color-primary)' : 'var(--color-border)'}`,
                          cursor: 'pointer', padding: 0, transition: 'border-color 120ms',
                        }}
                        title={color}
                      />
                    ))}
                    <label style={{ position: 'relative', display: 'inline-flex', alignItems: 'center', gap: 'var(--space-1)', padding: 'var(--space-1) var(--space-2)', border: '2px dashed var(--color-border)', borderRadius: 'var(--radius-sm)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', cursor: 'pointer' }} title="Custom color">
                      <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2v20M2 12h20"/></svg>
                      Custom
                      <input type="color" value={normalizeColor(information.color)} onChange={(e) => updateInfo('color', e.target.value)} style={{ position: 'absolute', inset: 0, opacity: 0, width: '100%', height: '100%', cursor: 'pointer' }} />
                    </label>
                  </div>
                </div>

                {/* True Ring + Tags */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
                  <input type="checkbox" id="isTrueRing" checked={information.isTrueRing} onChange={(e) => updateInfo('isTrueRing', e.target.checked)} style={{ width: 16, height: 16, cursor: 'pointer', accentColor: 'var(--color-primary)' }} />
                  <label htmlFor="isTrueRing" style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)', cursor: 'pointer' }}>
                    True Ring
                  </label>
                </div>

                {/* Custom Tags */}
                <div>
                  <SectionLabel>Custom Tags</SectionLabel>
                  <div style={{ display: 'flex', gap: 'var(--space-2)', marginBottom: 'var(--space-2)' }}>
                    <div style={{ flex: 1 }}>
                      <FormField label="" name="tag-input" type="text"
                        value={tagInput}
                        onChange={(e) => setTagInput(e.target.value)}
                        placeholder="Add tag…"
                        inputProps={{ onKeyDown: (e) => { if (e.key === 'Enter') { e.preventDefault(); addTag() } } }}
                      />
                    </div>
                    <div style={{ alignSelf: 'flex-end', paddingBottom: 1 }}>
                      <Button variant="secondary" type="button" onClick={addTag}>Add</Button>
                    </div>
                  </div>
                  {information.customTags.length > 0 && (
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-1)' }}>
                      {information.customTags.map((tag) => (
                        <span key={tag} style={{
                          display: 'inline-flex', alignItems: 'center', gap: 'var(--space-1)',
                          padding: '2px var(--space-2)',
                          borderRadius: 'var(--radius-full)',
                          backgroundColor: 'var(--color-primary-subtle)',
                          color: 'var(--color-primary)',
                          border: '1px solid var(--color-primary)',
                          fontSize: 'var(--font-size-xs)',
                        }}>
                          {tag}
                          <button type="button" onClick={() => removeTag(tag)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'inherit', lineHeight: 1, padding: 0, fontSize: 12 }}>×</button>
                        </span>
                      ))}
                    </div>
                  )}
                </div>
              </div>
            </Card>

            {/* Identifiers */}
            <Card title="Identifiers">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
                <FormField label="PID (Playback ID)" name="pid" type="text"
                  value={pid} onChange={(e) => setPid(e.target.value)}
                  placeholder="e.g. builtin_festive_vesper"
                />
                {url && (
                  <FormField label="URL (auto-set from binary upload)" name="url" type="text"
                    value={url} disabled
                    inputProps={{ readOnly: true, style: { fontFamily: 'var(--font-family-mono)', opacity: 0.7 } }}
                  />
                )}
              </div>
            </Card>

            {/* Admin Notes */}
            <Card title="Admin Notes">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
                {adminNotes.map((note, i) => (
                  <div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 'var(--space-2)' }}>
                    <div style={{ flex: 1, padding: 'var(--space-3)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-primary)' }}>
                      {note}
                    </div>
                    <Button variant="ghost" size="sm" type="button" onClick={() => setAdminNotes((prev) => prev.filter((_, j) => j !== i))}>×</Button>
                  </div>
                ))}
                <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
                  <div style={{ flex: 1 }}>
                    <FormField label="" name="new-note" type="textarea" rows={2}
                      value={newNote} onChange={(e) => setNewNote(e.target.value)}
                      placeholder="Add a note…"
                    />
                  </div>
                  <div style={{ alignSelf: 'flex-end', paddingBottom: 1 }}>
                    <Button variant="secondary" type="button" onClick={() => { if (newNote.trim()) { setAdminNotes((prev) => [...prev, newNote.trim()]); setNewNote('') } }}>
                      Add
                    </Button>
                  </div>
                </div>
              </div>
            </Card>
          </div>

          {/* ── Right column ────────────────────────────────────────────── */}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-6)' }}>

            {/* Default Settings */}
            <Card title="Default Settings">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>

                {/* Speed slider */}
                <div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 'var(--space-2)' }}>
                    <SectionLabel>Speed</SectionLabel>
                    <span style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-primary)' }}>
                      {settings.speed}%{speedBpm ? ` · ${speedBpm} bpm` : ''}{speedMs ? ` · ${speedMs} ms` : ''}
                    </span>
                  </div>
                  <input type="range" min="1" max="100" value={settings.speed}
                    onChange={(e) => updateSettings('speed', parseInt(e.target.value, 10))}
                    style={{ width: '100%', accentColor: 'var(--color-primary)', cursor: 'pointer' }}
                  />
                  {!speedBpm && (
                    <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginTop: 'var(--space-1)' }}>
                      Set Min/Max speed above to compute BPM
                    </p>
                  )}
                </div>

                {/* Duration slider */}
                <div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 'var(--space-2)' }}>
                    <SectionLabel>Duration</SectionLabel>
                    <span style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-primary)' }}>
                      {formatDuration(settings.duration)} ({settings.duration}s)
                    </span>
                  </div>
                  <input type="range" min="0" max={Math.max(0, durationValues.length - 1)}
                    value={currentDurIdx}
                    onChange={(e) => updateSettings('duration', durationValues[parseInt(e.target.value, 10)] ?? 0)}
                    style={{ width: '100%', accentColor: 'var(--color-primary)', cursor: 'pointer' }}
                  />
                </div>

                {/* Run / Pause duration */}
                <div className="grid grid-cols-2 gap-4">
                  <FormField label="Total Run Duration" name="totalRunDuration" type="number"
                    value={settings.totalRunDuration}
                    onChange={(e) => updateSettings('totalRunDuration', parseInt(e.target.value, 10) || 0)}
                    inputProps={{ min: 0 }}
                  />
                  <FormField label="Pause Duration" name="pauseDuration" type="number"
                    value={settings.pauseDuration}
                    onChange={(e) => updateSettings('pauseDuration', parseInt(e.target.value, 10) || 0)}
                    inputProps={{ min: 0 }}
                  />
                </div>

                {/* Infinite loop */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
                  <input type="checkbox" id="infiniteLoop" checked={settings.infiniteLoop} onChange={(e) => updateSettings('infiniteLoop', e.target.checked)} style={{ width: 16, height: 16, cursor: 'pointer', accentColor: 'var(--color-primary)' }} />
                  <label htmlFor="infiniteLoop" style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)', cursor: 'pointer' }}>
                    Infinite Loop
                  </label>
                </div>

                {/* Echo ring */}
                <FormField label="Echo Ring (comma-separated)" name="echoRing" type="text"
                  value={settings.echoRing.join(', ')}
                  onChange={(e) => updateSettings('echoRing', parseIntList(e.target.value))}
                  placeholder="e.g. 0, 1, 0, 1"
                />

                {/* Note assignments */}
                <div>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--space-2)' }}>
                    <SectionLabel>Note Assignments</SectionLabel>
                    <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', fontFamily: 'var(--font-family-mono)' }}>
                      {computeActiveBells(settings.noteAssignments)} active bell{computeActiveBells(settings.noteAssignments) !== 1 ? 's' : ''}
                    </span>
                  </div>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(8, 1fr)', gap: 'var(--space-2)' }}>
                    {Array.from({ length: information.totalNotes }, (_, i) => (
                      <div key={i}>
                        <div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginBottom: 2, textAlign: 'center' }}>
                          {String.fromCharCode(65 + i)}
                        </div>
                        <input
                          type="text"
                          inputMode="numeric"
                          value={settings.noteAssignments[i] ?? 0}
                          onChange={(e) => {
                            const na = [...settings.noteAssignments]
                            while (na.length <= i) na.push(0)
                            na[i] = parseInt(e.target.value, 10) || 0
                            updateSettings('noteAssignments', na)
                          }}
                          className="input"
                          style={{
                            width: '100%',
                            textAlign: 'center',
                            fontFamily: 'var(--font-family-mono)',
                            MozAppearance: 'textfield',
                          }}
                        />
                      </div>
                    ))}
                  </div>
                  <p style={{ marginTop: 'var(--space-1)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
                    0 = unassigned. Unique non-zero values count as active bells.
                  </p>
                </div>
              </div>
            </Card>

            {/* Files */}
            <Card title="Files">
              <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>

                {/* Binary */}
                <div>
                  <SectionLabel>Binary File (.bsm)</SectionLabel>
                  {binaryUrl ? (
                    <div style={{ marginBottom: 'var(--space-2)' }}>
                      {/* Main row: archetype name + Download pill + View pill + note count */}
                      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', flexWrap: 'wrap' }}>
                        <span style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)' }}>
                          {builtMelody?.name || binaryName}
                        </span>
                        {missingArchetype && (
                          <span title="Binary not found in Archetypes" style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-warning)', cursor: 'help' }}>⚠ Missing</span>
                        )}
                        {/* Download — blue pill */}
                        <button
                          type="button"
                          onClick={async (e) => {
                            e.preventDefault()
                            const melodyId = isEdit ? id : savedMelodyId
                            if (!melodyId) return
                            const endpoint = `/api/melodies/${melodyId}/download/binary`
                            try {
                              const token = localStorage.getItem('access_token')
                              const res = await fetch(endpoint, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
                              if (!res.ok) throw new Error(res.statusText)
                              const blob = await res.blob()
                              const objUrl = URL.createObjectURL(blob)
                              const a = document.createElement('a'); a.href = objUrl; a.download = binaryName; a.click()
                              URL.revokeObjectURL(objUrl)
                            } catch {
                              if (binaryUrl) { const a = document.createElement('a'); a.href = binaryUrl; a.download = binaryName; a.rel = 'noopener noreferrer'; document.body.appendChild(a); a.click(); document.body.removeChild(a) }
                            }
                          }}
                          style={{ padding: '2px 10px', borderRadius: 'var(--radius-full)', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-primary)', backgroundColor: 'var(--color-primary-subtle)', border: 'none', cursor: 'pointer' }}
                        >
                          Download
                        </button>
                        {/* View — neutral pill */}
                        <button
                          type="button"
                          onClick={() => setShowBinaryView(true)}
                          style={{ padding: '2px 10px', borderRadius: 'var(--radius-full)', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', cursor: 'pointer' }}
                        >
                          View
                        </button>
                        <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
                          {information.totalNotes ?? 0} active notes
                        </span>
                      </div>
                      {/* Subtitle: file: archetypename.bsm */}
                      <div style={{ marginTop: 'var(--space-1)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', fontFamily: 'var(--font-family-mono)' }}>
                        file: {binaryName}
                      </div>
                    </div>
                  ) : (
                    <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-2)' }}>
                      No binary assigned
                    </p>
                  )}
                  <div style={{ display: 'flex', gap: 'var(--space-2)', flexWrap: 'wrap' }}>
                    <Button variant="secondary" size="sm" type="button"
                      onClick={async () => { const mid = await ensureSaved(); if (mid) setShowSelectBuilt(true) }}>
                      Select Archetype
                    </Button>
                    <Button variant="secondary" size="sm" type="button"
                      onClick={async () => { const mid = await ensureSaved(); if (mid) setShowBuildOnTheFly(true) }}>
                      Build on the Fly
                    </Button>
                  </div>
                </div>

                {/* Audio preview upload */}
                <div>
                  <SectionLabel>Audio Preview</SectionLabel>
                  {normalizeFileUrl(existingFiles.preview_url) ? (
                    <div style={{ marginBottom: 'var(--space-2)' }}>
                      <audio controls src={normalizeFileUrl(existingFiles.preview_url)} style={{ height: 32, width: '100%' }} />
                    </div>
                  ) : previewFile ? (
                    <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-2)' }}>
                      <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-primary)' }}>{previewFile.name}</span>
                      <Button variant="ghost" size="sm" type="button" onClick={() => setPreviewFile(null)}>Remove</Button>
                    </div>
                  ) : null}
                  <Button variant="secondary" size="sm" type="button" onClick={() => previewInputRef.current?.click()}>
                    {existingFiles.preview_url ? 'Replace Preview' : 'Upload Preview'}
                  </Button>
                  <input ref={previewInputRef} type="file" accept="audio/*" style={{ display: 'none' }} onChange={(e) => setPreviewFile(e.target.files?.[0] || null)} />
                </div>
              </div>
            </Card>
          </div>
        </div>
      </form>

      {/* ── Modals ───────────────────────────────────────────────────────── */}
      <TranslationModal
        open={translationModal.open}
        onClose={() => setTranslationModal((p) => ({ ...p, open: false }))}
        field={translationModal.field}
        fieldKey={translationModal.fieldKey}
        multiline={translationModal.multiline}
        value={information[translationModal.fieldKey]}
        languages={languages}
        primaryLanguage={melodySettings?.primary_language || 'en'}
        onChange={(fieldKey, serialized) => updateInfo(fieldKey, serialized)}
      />

      <SelectArchetypeModal
        open={showSelectBuilt}
        onClose={() => setShowSelectBuilt(false)}
        melodyId={isEdit ? id : savedMelodyId}
        currentMelody={{ id: isEdit ? id : savedMelodyId, information, default_settings: settings, type, pid, url }}
        currentBuiltMelody={builtMelody}
        onAssigned={(bm) => {
          setBuiltMelody(bm)
          setAssignedBinaryPid(bm?.pid || null)
          setShowSelectBuilt(false)
          if (isEdit) loadMelody()
        }}
      />

      <BuildOnTheFlyModal
        open={showBuildOnTheFly}
        onClose={() => setShowBuildOnTheFly(false)}
        melodyId={isEdit ? id : savedMelodyId}
        currentMelody={{ id: isEdit ? id : savedMelodyId, information, default_settings: settings, type, pid, url }}
        currentBuiltMelody={builtMelody}
        defaultName={getLocalizedValue(information.name, 'en', '')}
        defaultPid={pid}
        onAssigned={(bm) => {
          setBuiltMelody(bm)
          setAssignedBinaryPid(bm?.pid || null)
          setShowBuildOnTheFly(false)
          if (isEdit) loadMelody()
        }}
      />

      <SpeedCalculatorModal
        open={showSpeedCalc}
        onClose={() => setShowSpeedCalc(false)}
        melody={{ id: isEdit ? id : savedMelodyId, information, default_settings: settings, type, pid, url }}
        builtMelody={builtMelody}
        archetypeCsv={information.archetype_csv || null}
        onSaved={() => { setShowSpeedCalc(false); if (isEdit) loadMelody() }}
      />

      <PlaybackModal
        open={showPlayback}
        onClose={() => setShowPlayback(false)}
        melody={{ id: isEdit ? id : savedMelodyId, information, default_settings: settings, type, pid, url }}
        builtMelody={builtMelody}
        files={existingFiles}
        archetypeCsv={information.archetype_csv || null}
      />

      <BinaryTableModal
        open={showBinaryView}
        onClose={() => setShowBinaryView(false)}
        melody={{ id: isEdit ? id : savedMelodyId, information, default_settings: settings, type, pid, url }}
        builtMelody={builtMelody}
        files={existingFiles}
        archetypeCsv={information.archetype_csv || null}
      />
    </div>
  )
}
