// frontend/src/pages/manufacturing/ProvisioningWizard.jsx
// Provisions an ESP32 board via WebSerial + esptool-js.
// Steps: 0=Mode  1=Select/Create device  2=Flash  3=Verify  4=Done

import { useState, useRef, useCallback, useEffect } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { ESPLoader, Transport } from 'esptool-js'
import api from '@/lib/api'
import PageHeader from '@/components/ui/PageHeader'
import Button from '@/components/ui/Button'
import Modal from '@/components/ui/Modal'
import StatusBadge from '@/components/ui/StatusBadge'
import SearchBar from '@/components/ui/SearchBar'
import Spinner from '@/components/ui/Spinner'
import Icon from '@/components/ui/Icon'

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

const BOARD_TYPES = [
  { value: 'vesper_pro',  name: 'VESPER PRO',   uiName: 'Vesper Pro',   codename: 'vesper-pro',    desc: 'Full-featured pro controller',  family: 'vesper'  },
  { value: 'vesper_plus', name: 'VESPER PLUS',  uiName: 'Vesper Plus',  codename: 'vesper-plus',   desc: 'Extended output controller',    family: 'vesper'  },
  { value: 'vesper',      name: 'VESPER',       uiName: 'Vesper',       codename: 'vesper-basic',  desc: 'Standard bell controller',      family: 'vesper'  },
  { value: 'agnus',       name: 'AGNUS',        uiName: 'Agnus',        codename: 'agnus-basic',   desc: 'Standard carillon module',      family: 'agnus'   },
  { value: 'agnus_mini',  name: 'AGNUS MINI',   uiName: 'Agnus Mini',   codename: 'agnus-mini',    desc: 'Compact carillon module',       family: 'agnus'   },
  { value: 'chronos_pro', name: 'CHRONOS PRO',  uiName: 'Chronos Pro',  codename: 'chronos-pro',   desc: 'Pro clock controller',          family: 'chronos' },
  { value: 'chronos',     name: 'CHRONOS',      uiName: 'Chronos',      codename: 'chronos-basic', desc: 'Basic clock controller',        family: 'chronos' },
]

const BOARD_TYPE_MAP = Object.fromEntries(BOARD_TYPES.map((b) => [b.value, b]))
const BOARD_TYPE_LABELS = Object.fromEntries(BOARD_TYPES.map((b) => [b.value, b.name]))

// Color palette per board family (idle → selected → hover glow)
const BOARD_FAMILY_COLORS = {
  vesper:  {
    selectedBg:     '#0a1929',
    selectedBorder: '#3b82f6',
    selectedText:   '#60a5fa',
    hoverBorder:    '#3b82f6',
    glowColor:      'rgba(59,130,246,0.35)',
    idleBorder:     '#1d3a5c',
    idleText:       '#7ca8d4',
  },
  agnus:   {
    selectedBg:     '#1a1400',
    selectedBorder: '#f59e0b',
    selectedText:   '#fbbf24',
    hoverBorder:    '#f59e0b',
    glowColor:      'rgba(245,158,11,0.35)',
    idleBorder:     '#4a3800',
    idleText:       '#c79d3a',
  },
  chronos: {
    selectedBg:     '#1a0808',
    selectedBorder: '#ef4444',
    selectedText:   '#f87171',
    hoverBorder:    '#ef4444',
    glowColor:      'rgba(239,68,68,0.35)',
    idleBorder:     '#5c1a1a',
    idleText:       '#d47a7a',
  },
}

const STATUS_VARIANT = {
  manufactured: 'neutral', flashed: 'info', provisioned: 'warning',
  sold: 'success', claimed: 'success', decommissioned: 'danger',
}

const FLASHABLE_STATUSES = ['manufactured', 'flashed', 'provisioned']

const FLASH_BAUD       = 460800
const NVS_ADDRESS      = 0x9000
const FW_ADDRESS       = 0x10000
const VERIFY_POLL_MS   = 5000
const VERIFY_TIMEOUT_MS = 120_000

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

function formatHwVersion(v) {
  if (!v) return '—'
  if (/^\d+\.\d+/.test(v)) return `Rev ${v}`
  const n = parseInt(v, 10)
  return isNaN(n) ? `Rev ${v}` : `Rev ${n}.0`
}

// ─── Step Indicator ───────────────────────────────────────────────────────────

const STEP_LABELS = ['Mode', 'Device', 'Flash', 'Verify', 'Done']

function CheckeredFlagIcon() {
  return (
    <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
      <path d="M4 2v20h2V13h14V3H4zm2 2h3v3H6V4zm0 5h3v3H6V9zm5-5h3v3h-3V4zm0 5h3v3h-3V9zm5-5h3v3h-3V4zm0 5h3v3h-3V9z" />
    </svg>
  )
}

function StepIndicator({ current }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
      {STEP_LABELS.map((label, i) => {
        const idx     = i + 1
        const done    = idx < current
        const active  = idx === current
        const pending = idx > current
        const isLast  = i === STEP_LABELS.length - 1

        const dotBg    = done   ? '#53b15786' : active ? '#22c55e' : '#251a1a'
        const dotColor = done   ? '#cbedb9'   : active ? '#ffffff' : '#555'
        const labelColor = active ? '#22c55e' : done ? '#53b15786' : '#555'
        const labelGlow  = active ? '0 0 8px rgba(34,197,94,0.45)' : 'none'
        const lineColor  = done ? '#22c55e' : 'var(--color-border)'
        const dotSize  = active ? 38 : 30

        return (
          <div key={label} style={{ display: 'flex', alignItems: 'center' }}>
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', flexShrink: 0 }}>
              <div style={{
                width: dotSize, height: dotSize, borderRadius: '60%',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                backgroundColor: dotBg, color: dotColor,
                fontSize: active ? '1.0rem' : '0.85rem', fontWeight: 500,
                border: active ? '2px solid #22c55e' : done ? '2px solid #6d9b78c0' : '2px solid #333',
                boxShadow: active ? '0 0 18px 8px rgba(34,197,94,0.4)' : 'none',
                transition: 'all 0.2s',
              }}>
                {done ? (
                  <svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
                  </svg>
                ) : isLast ? <CheckeredFlagIcon /> : idx}
              </div>
              <span style={{
                fontSize: 'var(--font-size-xs)', marginTop: 4,
                fontWeight: active ? 'var(--font-weight-semibold)' : 'var(--font-weight-normal)',
                color: labelColor, textShadow: labelGlow,
                opacity: pending ? 0.5 : 1, maxWidth: 72, textAlign: 'center', lineHeight: 1.2,
                whiteSpace: 'nowrap',
              }}>{label}</span>
            </div>
            {i < STEP_LABELS.length - 1 && (
              <div style={{
                height: 2, flex: '1 1 16px', minWidth: 28, maxWidth: 40,
                backgroundColor: lineColor,
                marginBottom: 18, marginLeft: 4, marginRight: 4,
                transition: 'background-color 0.2s', flexShrink: 1,
              }} />
            )}
          </div>
        )
      })}
    </div>
  )
}

// ─── Board Tile ───────────────────────────────────────────────────────────────

function BoardTile({ bt, isSelected, onClick }) {
  const [hovered, setHovered] = useState(false)
  const pal = BOARD_FAMILY_COLORS[bt.family]

  const borderColor = isSelected ? pal.selectedBorder : hovered ? pal.hoverBorder : pal.idleBorder
  const boxShadow   = isSelected
    ? `0 0 0 1px ${pal.selectedBorder}, 0 0 14px 4px ${pal.glowColor}`
    : hovered
    ? `0 0 12px 3px ${pal.glowColor}`
    : 'none'

  return (
    <button
      type="button"
      onClick={onClick}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      aria-pressed={isSelected}
      style={{
        backgroundColor: isSelected ? pal.selectedBg : 'var(--color-bg-elevated)',
        border: `1px solid ${borderColor}`,
        borderRadius: 'var(--radius-md)',
        padding: 'var(--space-3)',
        textAlign: 'left', cursor: 'pointer',
        transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.15s',
        boxShadow,
        width: '100%',
      }}
    >
      <p style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-bold)', fontFamily: 'var(--font-family-display)', letterSpacing: 'var(--tracking-wide)', color: isSelected ? pal.selectedText : hovered ? pal.idleText : 'var(--color-text-primary)', marginBottom: 2 }}>{bt.name}</p>
      <p style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-text-muted)', opacity: 0.7, marginBottom: 3 }}>{bt.codename}</p>
      <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', opacity: isSelected ? 0.9 : 0.6, lineHeight: 1.4 }}>{bt.desc}</p>
    </button>
  )
}

// ─── Progress Bar ─────────────────────────────────────────────────────────────

function ProgressBar({ label, percent, flex = false }) {
  return (
    <div style={{ flex: flex ? 1 : undefined }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--space-1)' }}>
        <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-secondary)' }}>{label}</span>
        <span style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-text-muted)' }}>{Math.round(percent)}%</span>
      </div>
      <div style={{ height: 6, borderRadius: 'var(--radius-full)', backgroundColor: 'var(--color-bg-elevated)', overflow: 'hidden' }}>
        <div style={{
          height: '100%', width: `${percent}%`, borderRadius: 'var(--radius-full)',
          background: 'var(--gradient-primary)', transition: 'width 0.2s ease',
          boxShadow: percent > 0 ? 'var(--shadow-primary-glow)' : 'none',
        }} />
      </div>
    </div>
  )
}

// ─── Info Cell ────────────────────────────────────────────────────────────────

function InfoCell({ label, value, mono = false }) {
  return (
    <div>
      <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', fontWeight: 'var(--font-weight-semibold)', marginBottom: 'var(--space-1)' }}>{label}</p>
      {typeof value === 'string'
        ? <p style={{ fontSize: 'var(--font-size-sm)', fontFamily: mono ? 'var(--font-family-mono)' : undefined, color: 'var(--color-text-primary)' }}>{value || '—'}</p>
        : value}
    </div>
  )
}

// ─── Error Box ────────────────────────────────────────────────────────────────

function ErrorBox({ msg }) {
  if (!msg) return null
  return (
    <div style={{ padding: 'var(--space-3)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-danger-bg)', border: '1px solid var(--color-danger)', color: 'var(--color-danger)', fontSize: 'var(--font-size-sm)' }}>
      {msg}
    </div>
  )
}

// ─── Bespoke Picker Modal ─────────────────────────────────────────────────────

function BespokePickerModal({ open, onConfirm, onClose }) {
  const [firmwares, setFirmwares] = useState([])
  const [loading, setLoading]     = useState(true)
  const [error, setError]         = useState('')
  const [selected, setSelected]   = useState(null)
  const [hwFamily, setHwFamily]   = useState('')

  useEffect(() => {
    if (!open) return
    setLoading(true)
    api.get('/firmware?hw_type=bespoke')
      .then((data) => setFirmwares(data.firmware || []))
      .catch((err) => setError(err.message))
      .finally(() => setLoading(false))
  }, [open])

  const handleConfirm = () => {
    if (!selected) return
    onConfirm({ firmware: selected, hwFamily })
  }

  return (
    <Modal
      open={open}
      onClose={onClose}
      title="Select Bespoke Firmware"
      size="md"
      footer={
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
          <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
            hw_revision will be set to <span style={{ fontFamily: 'var(--font-family-mono)' }}>1.0</span> for bespoke devices.
          </p>
          <div style={{ display: 'flex', gap: 'var(--space-2)' }}>
            <Button variant="ghost" onClick={onClose}>Cancel</Button>
            <Button variant="primary" onClick={handleConfirm} disabled={!selected || !hwFamily.trim()}>
              Continue
            </Button>
          </div>
        </div>
      }
    >
      <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
        <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
          Choose a bespoke firmware and the hardware family to register in NVS.
        </p>

        {loading && <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-6)' }}><Spinner /></div>}
        {error && <ErrorBox msg={error} />}
        {!loading && !error && firmwares.length === 0 && (
          <p style={{ textAlign: 'center', padding: 'var(--space-6)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
            No bespoke firmwares uploaded yet. Upload one from the Firmware Manager.
          </p>
        )}

        {firmwares.length > 0 && (
          <div style={{ border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', overflow: 'hidden' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse' }}>
              <thead>
                <tr style={{ backgroundColor: 'var(--color-bg-elevated)', borderBottom: '1px solid var(--color-border)' }}>
                  {['UID', 'Version', 'Channel', 'Size'].map((h) => (
                    <th key={h} style={{ padding: 'var(--space-2) var(--space-3)', textAlign: 'left', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)' }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {firmwares.map((fw) => {
                  const isSel = selected?.id === fw.id
                  return (
                    <tr
                      key={fw.id}
                      onClick={() => setSelected(fw)}
                      style={{ borderBottom: '1px solid var(--color-border)', cursor: 'pointer', transition: 'background-color 0.1s', backgroundColor: isSel ? 'var(--color-info-bg)' : '' }}
                      onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.backgroundColor = 'var(--color-bg-elevated)' }}
                      onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.backgroundColor = '' }}
                    >
                      <td style={{ padding: 'var(--space-2) var(--space-3)', fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-primary)' }}>{fw.bespoke_uid || '—'}</td>
                      <td style={{ padding: 'var(--space-2) var(--space-3)', fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-secondary)' }}>{fw.version}</td>
                      <td style={{ padding: 'var(--space-2) var(--space-3)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>{fw.channel}</td>
                      <td style={{ padding: 'var(--space-2) var(--space-3)', fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>{fw.size_bytes ? `${(fw.size_bytes / 1024).toFixed(1)} KB` : '—'}</td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
          </div>
        )}

        <div>
          <label style={{ display: 'block', fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-1)' }}>
            Hardware Family for NVS
          </label>
          <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-2)' }}>
            Written to NVS as <span style={{ fontFamily: 'var(--font-family-mono)' }}>hw_type</span>.
          </p>
          <input
            type="text"
            value={hwFamily}
            onChange={(e) => setHwFamily(e.target.value)}
            placeholder="e.g. vesper_plus"
            style={{ width: '100%', padding: 'var(--space-2) var(--space-3)', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-strong)', backgroundColor: 'var(--color-bg-input)', color: 'var(--color-text-primary)', fontSize: 'var(--font-size-sm)' }}
          />
        </div>
      </div>
    </Modal>
  )
}

// ─── Step 0: Mode Picker ──────────────────────────────────────────────────────

function StepMode({ onPick }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>
      <div>
        <h2 style={{ fontSize: 'var(--font-size-lg)', fontFamily: 'var(--font-family-display)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>
          What would you like to do?
        </h2>
        <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
          Choose how to start the provisioning process.
        </p>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-4)' }}>
        {/* Flash Existing */}
        <button
          type="button"
          onClick={() => onPick('existing')}
          style={{
            backgroundColor: 'var(--color-bg-elevated)',
            border: '1px solid var(--color-border)',
            borderRadius: 'var(--radius-lg)',
            padding: 'var(--space-5)',
            textAlign: 'left', cursor: 'pointer',
            transition: 'border-color 0.15s, box-shadow 0.15s',
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.borderColor = 'var(--color-primary)'
            e.currentTarget.style.boxShadow = '0 0 16px rgba(34,197,94,0.18)'
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.borderColor = 'var(--color-border)'
            e.currentTarget.style.boxShadow = 'none'
          }}
        >
          <div style={{ width: 40, height: 40, borderRadius: 'var(--radius-lg)', backgroundColor: 'var(--color-primary-subtle)', border: '1px solid var(--color-primary)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 'var(--space-3)' }}>
            <svg width="18" height="18" fill="none" stroke="var(--color-primary)" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
            </svg>
          </div>
          <p style={{ fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>Flash Existing</p>
          <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', lineHeight: 1.5 }}>
            Re-flash a device already in inventory — manufactured, flashed, or provisioned.
          </p>
        </button>

        {/* Deploy New Device */}
        <button
          type="button"
          onClick={() => onPick('new')}
          style={{
            backgroundColor: 'var(--color-bg-elevated)',
            border: '1px solid var(--color-border)',
            borderRadius: 'var(--radius-lg)',
            padding: 'var(--space-5)',
            textAlign: 'left', cursor: 'pointer',
            transition: 'border-color 0.15s, box-shadow 0.15s',
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.borderColor = 'var(--color-success)'
            e.currentTarget.style.boxShadow = '0 0 16px rgba(34,197,94,0.18)'
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.borderColor = 'var(--color-border)'
            e.currentTarget.style.boxShadow = 'none'
          }}
        >
          <div style={{ width: 40, height: 40, borderRadius: 'var(--radius-lg)', backgroundColor: 'var(--color-success-bg)', border: '1px solid var(--color-success)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 'var(--space-3)' }}>
            <svg width="18" height="18" fill="none" stroke="var(--color-success)" viewBox="0 0 24 24">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
            </svg>
          </div>
          <p style={{ fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>Deploy New Device</p>
          <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', lineHeight: 1.5 }}>
            Generate a new serial number, select board type and revision, then flash and provision.
          </p>
        </button>
      </div>
    </div>
  )
}

// ─── Step 1: Select / Create Device ──────────────────────────────────────────

function StepSelectDevice({ mode, preloadSn, onSelected, onCreatedSn }) {
  const [search, setSearch]     = useState(preloadSn || '')
  const [results, setResults]   = useState([])
  const [loading, setLoading]   = useState(false)
  const [error, setError]       = useState('')
  const [picked, setPicked]     = useState(null)

  // New device form
  const [boardType, setBoardType]           = useState(null)
  const [boardVersion, setBoardVersion]     = useState('1.0')
  const [creating, setCreating]             = useState(false)
  const [showBespoke, setShowBespoke]       = useState(false)

  const doSearch = useCallback(async (q) => {
    setLoading(true); setError('')
    try {
      const params = new URLSearchParams({ limit: '50' })
      if (q) params.set('search', q)
      const data = await api.get(`/manufacturing/devices?${params}`)
      const all  = data.devices || []
      setResults(all.filter((d) => FLASHABLE_STATUSES.includes(d.mfg_status)))
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => { if (mode === 'existing') doSearch(preloadSn || '') }, [mode, doSearch, preloadSn])

  const handleCreate = async () => {
    if (!boardType) return
    setCreating(true); setError('')
    try {
      const batch  = await api.post('/manufacturing/batch', { board_type: boardType, board_version: boardVersion.trim(), quantity: 1 })
      const sn     = batch.serial_numbers[0]
      onCreatedSn(sn)
      const device = await api.get(`/manufacturing/devices/${sn}`)
      onSelected(device)
    } catch (err) {
      setError(err.message)
    } finally {
      setCreating(false)
    }
  }

  const handleBespokeConfirm = async ({ firmware, hwFamily }) => {
    setShowBespoke(false)
    setCreating(true); setError('')
    try {
      const batch  = await api.post('/manufacturing/batch', { board_type: 'vesper', board_version: '1.0', quantity: 1 })
      const sn     = batch.serial_numbers[0]
      onCreatedSn(sn)
      const device = await api.get(`/manufacturing/devices/${sn}`)
      onSelected(device, { firmware, hwFamily })
    } catch (err) {
      setError(err.message)
    } finally {
      setCreating(false)
    }
  }

  // ── Selected confirmation panel ──
  if (picked) {
    const boardInfo = BOARD_TYPE_MAP[picked.hw_type]
    const pal = BOARD_FAMILY_COLORS[boardInfo?.family || 'vesper']
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
        <div style={{ padding: 'var(--space-4)', backgroundColor: 'var(--color-bg-elevated)', border: `1px solid ${pal.selectedBorder}`, borderRadius: 'var(--radius-lg)' }}>
          <p style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: pal.selectedText, textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-3)' }}>Device Selected</p>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-3)' }}>
            <InfoCell label="Serial Number" value={picked.serial_number} mono />
            <InfoCell label="Board Type" value={BOARD_TYPE_LABELS[picked.hw_type] || picked.hw_type} />
            <InfoCell label="HW Revision" value={formatHwVersion(picked.hw_version)} />
            <InfoCell label="Status" value={<StatusBadge variant={STATUS_VARIANT[picked.mfg_status] || 'neutral'}>{picked.mfg_status}</StatusBadge>} />
          </div>
        </div>
        <div style={{ display: 'flex', gap: 'var(--space-3)' }}>
          <Button variant="primary" onClick={() => onSelected(picked)}>
            Continue to Flash <Icon name="arrow-right" size={14} />
          </Button>
          <Button variant="ghost" onClick={() => setPicked(null)}>Change Device</Button>
        </div>
      </div>
    )
  }

  // ── Deploy New form ──
  if (mode === 'new') {
    const vesperBoards  = BOARD_TYPES.filter((b) => b.family === 'vesper')
    const agnusBoards   = BOARD_TYPES.filter((b) => b.family === 'agnus')
    const chronosBoards = BOARD_TYPES.filter((b) => b.family === 'chronos')

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}>
        {/* Board type */}
        <div>
          <p style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-secondary)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-3)' }}>Board Type</p>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
            {/* Vesper row — 3 equal columns */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--space-2)' }}>
              {vesperBoards.map((bt) => (
                <BoardTile key={bt.value} bt={bt} isSelected={boardType === bt.value} onClick={() => setBoardType(bt.value)} />
              ))}
            </div>
            {/* Agnus row — 2 boards centered inside the same 3-col grid */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--space-2)' }}>
              <div style={{ gridColumn: '1 / 3', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-2)' }}>
                {agnusBoards.map((bt) => (
                  <BoardTile key={bt.value} bt={bt} isSelected={boardType === bt.value} onClick={() => setBoardType(bt.value)} />
                ))}
              </div>
            </div>
            {/* Chronos row — 2 boards centered inside the same 3-col grid */}
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 'var(--space-2)' }}>
              <div style={{ gridColumn: '1 / 3', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-2)' }}>
                {chronosBoards.map((bt) => (
                  <BoardTile key={bt.value} bt={bt} isSelected={boardType === bt.value} onClick={() => setBoardType(bt.value)} />
                ))}
              </div>
            </div>
          </div>
        </div>

        {/* Board revision — narrow */}
        <div style={{ display: 'flex', alignItems: 'flex-end', gap: 'var(--space-4)' }}>
          <div>
            <label style={{ display: 'block', fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-medium)', color: 'var(--color-text-secondary)', marginBottom: 'var(--space-1)' }}>
              Board Revision
            </label>
            <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
              <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>Rev</span>
              <input
                type="text"
                value={boardVersion}
                onChange={(e) => setBoardVersion(e.target.value)}
                placeholder="1.0"
                style={{
                  width: 72, padding: 'var(--space-2) var(--space-2)',
                  borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border-strong)',
                  backgroundColor: 'var(--color-bg-input)', color: 'var(--color-text-primary)',
                  fontSize: 'var(--font-size-sm)', textAlign: 'center',
                }}
              />
            </div>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', marginTop: 'var(--space-1)' }}>e.g. 1.0, 1.20</p>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 'var(--space-2)', marginLeft: 'auto' }}>
            <ErrorBox msg={error} />
            <Button variant="primary" onClick={handleCreate} loading={creating} disabled={!boardType}>
              Generate Serial &amp; Continue <Icon name="arrow-right" size={14} />
            </Button>
          </div>
        </div>

        {/* Bespoke divider */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
          <div style={{ flex: 1, height: 1, backgroundColor: 'var(--color-border)' }} />
          <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>or</span>
          <div style={{ flex: 1, height: 1, backgroundColor: 'var(--color-border)' }} />
        </div>

        {/* Bespoke option */}
        <div style={{ padding: 'var(--space-4)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-lg)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 'var(--space-4)' }}>
          <div>
            <p style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>Select Bespoke Firmware</p>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
              Flash a one-off bespoke firmware with a custom hardware family written to NVS.
            </p>
          </div>
          <Button variant="secondary" onClick={() => setShowBespoke(true)} style={{ flexShrink: 0 }}>
            Select Bespoke →
          </Button>
        </div>

        <BespokePickerModal open={showBespoke} onConfirm={handleBespokeConfirm} onClose={() => setShowBespoke(false)} />
      </div>
    )
  }

  // ── Existing device search ──
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
      <SearchBar
        value={search}
        onChange={(v) => { setSearch(v); doSearch(v) }}
        placeholder="Search serial, batch, type…"
      />

      {error && <ErrorBox msg={error} />}

      {loading ? (
        <div style={{ display: 'flex', justifyContent: 'center', padding: 'var(--space-8)' }}><Spinner /></div>
      ) : results.length === 0 ? (
        <p style={{ textAlign: 'center', padding: 'var(--space-8)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>No flashable devices found.</p>
      ) : (
        <div style={{ border: '1px solid var(--color-border)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr style={{ backgroundColor: 'var(--color-bg-elevated)', borderBottom: '1px solid var(--color-border)' }}>
                {['Serial', 'Type', 'Revision', 'Status'].map((h) => (
                  <th key={h} style={{ padding: 'var(--space-2) var(--space-3)', textAlign: 'left', fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)' }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {results.map((d, i) => (
                <tr
                  key={d.id}
                  onClick={() => setPicked(d)}
                  style={{ borderBottom: i < results.length - 1 ? '1px solid var(--color-border)' : 'none', cursor: 'pointer', transition: 'background-color 0.1s' }}
                  onMouseEnter={(e) => { e.currentTarget.style.backgroundColor = 'var(--color-bg-elevated)' }}
                  onMouseLeave={(e) => { e.currentTarget.style.backgroundColor = '' }}
                >
                  <td style={{ padding: 'var(--space-3)', fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-primary)' }}>{d.serial_number}</td>
                  <td style={{ padding: 'var(--space-3)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-secondary)' }}>{BOARD_TYPE_LABELS[d.hw_type] || d.hw_type}</td>
                  <td style={{ padding: 'var(--space-3)', fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>{formatHwVersion(d.hw_version)}</td>
                  <td style={{ padding: 'var(--space-3)' }}>
                    <StatusBadge variant={STATUS_VARIANT[d.mfg_status] || 'neutral'}>{d.mfg_status}</StatusBadge>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </div>
  )
}

// ─── Serial Log Modal ──────────────────────────────────────────────────────────

function SerialLogModal({ open, onClose, logs }) {
  const [autoScroll, setAutoScroll] = useState(true)
  const endRef = useRef(null)

  useEffect(() => {
    if (autoScroll && open) endRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [logs, autoScroll, open])

  return (
    <Modal
      open={open}
      onClose={onClose}
      title="Serial Output Logs"
      size="lg"
      footer={
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
          <label style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', cursor: 'pointer', userSelect: 'none' }}>
            <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>Auto-scroll</span>
            <span
              onClick={() => setAutoScroll((v) => !v)}
              style={{
                position: 'relative', display: 'inline-flex', alignItems: 'center',
                width: 32, height: 18,
                backgroundColor: autoScroll ? 'var(--color-primary)' : 'var(--color-bg-elevated)',
                borderRadius: 9, border: '1px solid var(--color-border)',
                cursor: 'pointer', transition: 'background-color 0.2s', flexShrink: 0,
              }}
            >
              <span style={{
                position: 'absolute', left: autoScroll ? 14 : 2,
                width: 14, height: 14, borderRadius: '50%',
                backgroundColor: '#fff', transition: 'left 0.15s',
              }} />
            </span>
          </label>
          <Button variant="secondary" onClick={onClose}>Close</Button>
        </div>
      }
    >
      <div style={{
        backgroundColor: 'var(--color-bg-abyss)',
        border: '1px solid var(--color-border)',
        borderRadius: 'var(--radius-md)',
        padding: 'var(--space-3)',
        height: 400,
        overflowY: 'auto',
        fontFamily: 'var(--font-family-mono)',
        fontSize: 'var(--font-size-xs)',
        lineHeight: 1.6,
        color: '#a3e635',
      }}>
        {logs.length === 0 ? (
          <span style={{ color: 'var(--color-text-muted)', opacity: 0.5 }}>No serial output yet.</span>
        ) : (
          logs.map((line, i) => <div key={i}>{line}</div>)
        )}
        <div ref={endRef} />
      </div>
    </Modal>
  )
}

// ─── Step 2: Flash ────────────────────────────────────────────────────────────

function StepFlash({ device, bespokeOverride, onFlashed }) {
  const [portConnected, setPortConnected]       = useState(false)
  const [portName, setPortName]                 = useState('')
  const [connecting, setConnecting]             = useState(false)
  const [flashing, setFlashing]                 = useState(false)
  const [done, setDone]                         = useState(false)
  const [blProgress, setBlProgress]             = useState(0)
  const [partProgress, setPartProgress]         = useState(0)
  const [nvsProgress, setNvsProgress]           = useState(0)
  const [fwProgress, setFwProgress]             = useState(0)
  const [log, setLog]                           = useState([])
  const [serial, setSerial]                     = useState([])
  const [nvsProfile, setNvsProfile]             = useState('current')
  const [error, setError]                       = useState('')

  const loaderRef           = useRef(null)
  const portRef             = useRef(null)
  const serialReaderRef     = useRef(null)
  const serialActiveRef     = useRef(false)
  const logEndRef           = useRef(null)
  const serialEndRef        = useRef(null)

  const appendLog    = (msg) => setLog((prev) => [...prev, String(msg)])
  const appendSerial = (msg) => setSerial((prev) => [...prev, String(msg)])

  const scrollLog    = () => logEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  const scrollSerial = () => serialEndRef.current?.scrollIntoView({ behavior: 'smooth' })

  const fetchBinary = async (url) => {
    const token = localStorage.getItem('access_token')
    const resp  = await fetch(url, { headers: { Authorization: `Bearer ${token}` } })
    if (!resp.ok) {
      const err = await resp.json().catch(() => ({}))
      throw new Error(err.detail || `Failed to fetch ${url}: ${resp.status}`)
    }
    return resp.arrayBuffer()
  }

  const arrayBufferToString = (buf) => {
    const bytes = new Uint8Array(buf)
    let str = ''
    for (let i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i])
    return str
  }

  const startSerialMonitor = async (port) => {
    serialActiveRef.current = true
    await new Promise((r) => setTimeout(r, 1000))
    try { await port.open({ baudRate: 115200 }) } catch (openErr) {
      appendSerial(`[Error opening port: ${openErr.message}]`); scrollSerial(); return
    }
    let reader
    try { reader = port.readable.getReader() } catch (readerErr) {
      appendSerial(`[Error getting reader: ${readerErr.message}]`); scrollSerial()
      try { await port.close() } catch (_) {}
      return
    }
    serialReaderRef.current = reader
    const dec = new TextDecoder()
    let buf = ''
    try {
      while (serialActiveRef.current) {
        const { value, done: streamDone } = await reader.read()
        if (streamDone) break
        buf += dec.decode(value, { stream: true })
        const lines = buf.split(/\r?\n/)
        buf = lines.pop()
        for (const line of lines) { if (line.trim()) { appendSerial(line); scrollSerial() } }
      }
    } catch (_) {}
    finally { try { reader.releaseLock() } catch (_) {} }
  }

  const disconnectPort = async () => {
    serialActiveRef.current = false
    try { await serialReaderRef.current?.cancel() } catch (_) {}
    try { serialReaderRef.current?.releaseLock() } catch (_) {}
    try { await portRef.current?.close() } catch (_) {}
    portRef.current = null
    setPortConnected(false)
    setPortName('')
    appendSerial('[Port disconnected]')
  }

  const handleConnectPort = async () => {
    setError(''); setConnecting(true)
    try {
      const port  = await navigator.serial.requestPort()
      portRef.current = port
      const info  = port.getInfo?.() || {}
      const label = info.usbVendorId
        ? `USB ${info.usbVendorId.toString(16).toUpperCase()}:${(info.usbProductId || 0).toString(16).toUpperCase()}`
        : 'Serial Port'
      setPortName(label)
      setPortConnected(true)
    } catch (err) {
      setError(err.message || 'Port selection cancelled.')
    } finally {
      setConnecting(false)
    }
  }

  const handleStartFlash = async () => {
    if (!portRef.current) return
    setError(''); setLog([]); setSerial([])
    setBlProgress(0); setPartProgress(0); setNvsProgress(0); setFwProgress(0)
    setDone(false)

    const port = portRef.current
    const sn   = device.serial_number

    try {
      const blUrl = bespokeOverride
        ? `/api/manufacturing/devices/${sn}/bootloader.bin?hw_type_override=${bespokeOverride.hwFamily}`
        : `/api/manufacturing/devices/${sn}/bootloader.bin`
      const partUrl = bespokeOverride
        ? `/api/manufacturing/devices/${sn}/partitions.bin?hw_type_override=${bespokeOverride.hwFamily}`
        : `/api/manufacturing/devices/${sn}/partitions.bin`
      const nvsUrl = bespokeOverride
        ? `/api/manufacturing/devices/${sn}/nvs.bin?hw_type_override=${bespokeOverride.hwFamily}&hw_revision_override=1.0&nvs_schema=${nvsProfile}`
        : `/api/manufacturing/devices/${sn}/nvs.bin?nvs_schema=${nvsProfile}`
      const fwUrl = bespokeOverride
        ? `/api/firmware/bespoke/${bespokeOverride.firmware.channel}/${bespokeOverride.firmware.version}/firmware.bin`
        : `/api/manufacturing/devices/${sn}/firmware.bin`

      appendLog('Fetching bootloader binary…')
      const blBuffer   = await fetchBinary(blUrl);   appendLog(`Bootloader: ${blBuffer.byteLength} bytes`)
      appendLog('Fetching partition table binary…')
      const partBuffer = await fetchBinary(partUrl); appendLog(`Partition table: ${partBuffer.byteLength} bytes`)
      appendLog('Fetching NVS binary…')
      const nvsBuffer  = await fetchBinary(nvsUrl);  appendLog(`NVS: ${nvsBuffer.byteLength} bytes`)
      appendLog('Fetching firmware binary…')
      const fwBuffer   = await fetchBinary(fwUrl);   appendLog(`Firmware: ${fwBuffer.byteLength} bytes`)

      setFlashing(true)
      appendLog('Connecting to ESP32…')
      const transport = new Transport(port, true)
      loaderRef.current = new ESPLoader({
        transport, baudrate: FLASH_BAUD,
        terminal: {
          clean() {},
          writeLine: (line) => { appendLog(line); scrollLog() },
          write:     (msg)  => { appendLog(msg);  scrollLog() },
        },
      })
      await loaderRef.current.main()
      appendLog('ESP32 connected.')

      await loaderRef.current.writeFlash({
        fileArray: [
          { data: arrayBufferToString(blBuffer),   address: 0x1000      },
          { data: arrayBufferToString(partBuffer), address: 0x8000      },
          { data: arrayBufferToString(nvsBuffer),  address: NVS_ADDRESS },
          { data: arrayBufferToString(fwBuffer),   address: FW_ADDRESS  },
        ],
        flashSize: 'keep', flashMode: 'keep', flashFreq: 'keep',
        eraseAll: false, compress: true,
        reportProgress(fileIndex, written, total) {
          const pct = (written / total) * 100
          if      (fileIndex === 0) { setBlProgress(pct) }
          else if (fileIndex === 1) { setBlProgress(100); setPartProgress(pct) }
          else if (fileIndex === 2) { setPartProgress(100); setNvsProgress(pct) }
          else                      { setNvsProgress(100); setFwProgress(pct) }
        },
        calculateMD5Hash: () => '',
      })

      setBlProgress(100); setPartProgress(100); setNvsProgress(100); setFwProgress(100)
      appendLog('Flash complete. Resetting device…')

      try {
        const t = loaderRef.current.transport
        await t.setRTS(true);  await new Promise((r) => setTimeout(r, 100))
        await t.setRTS(false); await new Promise((r) => setTimeout(r, 100))
      } catch (rstErr) { appendLog(`[Reset warning: ${rstErr.message}]`) }

      appendLog('Hard reset sent. Device is booting…')
      try { await loaderRef.current.transport.disconnect() } catch (_) {}
      appendLog('esptool disconnected. Opening serial monitor…')

      await api.request(`/manufacturing/devices/${sn}/status`, {
        method: 'PATCH',
        body: JSON.stringify({ status: 'flashed', note: 'Flashed via browser provisioning wizard' }),
      })

      setFlashing(false)
      setDone(true)
      appendSerial('── Serial monitor started (115200 baud) ──')
      startSerialMonitor(port)
    } catch (err) {
      setError(err.message || String(err))
      setFlashing(false)
      try { await loaderRef.current?.transport?.disconnect() } catch (_) {}
    }
  }

  const webSerialAvailable = typeof navigator !== 'undefined' && 'serial' in navigator
  const busy = connecting || flashing
  const boardInfo = BOARD_TYPE_MAP[device.hw_type]

  const familyPal = BOARD_FAMILY_COLORS[boardInfo?.family || 'vesper']

  // ── Left panel: device info + controls ────────────────────────────────────
  const InfoPanel = (
    <div style={{
      borderRadius: 'var(--radius-lg)',
      border: `1px solid ${familyPal.idleBorder}`,
      padding: 'var(--space-5)',
      backgroundColor: 'var(--color-bg-elevated)',
      display: 'flex', flexDirection: 'column',
    }}>
      {/* Header: title + COM status button */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-4)' }}>
        <p style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', color: familyPal.idleText }}>
          Device to Flash
        </p>
        <button
          onClick={portConnected ? disconnectPort : undefined}
          title={portConnected ? `${portName} — Click to disconnect` : 'No port connected'}
          style={{
            display: 'flex', alignItems: 'center', gap: 6,
            padding: '4px 10px', borderRadius: 'var(--radius-md)',
            fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-medium)',
            cursor: portConnected ? 'pointer' : 'default',
            backgroundColor: portConnected ? '#0a2e1a' : 'var(--color-bg-base)',
            color: portConnected ? '#4dd6c8' : 'var(--color-text-muted)',
            border: `1px solid ${portConnected ? '#4dd6c8' : 'var(--color-border)'}`,
            transition: 'opacity 0.15s',
          }}
          onMouseEnter={(e) => { if (portConnected) e.currentTarget.style.opacity = '0.75' }}
          onMouseLeave={(e) => { e.currentTarget.style.opacity = '1' }}
        >
          <span style={{ width: 8, height: 8, borderRadius: '50%', backgroundColor: portConnected ? '#22c55e' : '#444', display: 'inline-block', flexShrink: 0 }} />
          {portConnected ? portName || 'Connected' : 'No Port'}
          {portConnected && <span style={{ marginLeft: 2, opacity: 0.6 }}>✕</span>}
        </button>
      </div>

      {/* Device info */}
      <div style={{ marginBottom: 'var(--space-4)' }}>
        {bespokeOverride ? (
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-3)' }}>
            <InfoCell label="Serial Number" value={device.serial_number} mono />
            <InfoCell label="NVS hw_type" value={bespokeOverride.hwFamily} />
            <InfoCell label="NVS hw_revision" value="1.0" />
            <div>
              <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', fontWeight: 'var(--font-weight-semibold)', marginBottom: 'var(--space-1)' }}>Firmware</p>
              <p style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: '#fb923c' }}>BESPOKE · {bespokeOverride.firmware.bespoke_uid}</p>
              <p style={{ fontSize: 'var(--font-size-xs)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-text-muted)', marginTop: 2 }}>v{bespokeOverride.firmware.version} / {bespokeOverride.firmware.channel}</p>
            </div>
          </div>
        ) : (
          <>
            {/* Row 1: serial number + status badge */}
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 'var(--space-2)' }}>
              <p style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)' }}>
                {device.serial_number}
              </p>
              <StatusBadge variant={STATUS_VARIANT[device.mfg_status] || 'neutral'}>{device.mfg_status}</StatusBadge>
            </div>
            {/* Row 2: board type + codename */}
            <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)', marginBottom: 'var(--space-1)' }}>
              Board Type:
              <span style={{ display: 'inline-block', width: 'var(--space-3)' }} />
              <span style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-primary)' }}>
                {boardInfo?.uiName || boardInfo?.name || device.hw_type}
              </span>
              {boardInfo?.codename && (
                <span style={{ fontFamily: 'var(--font-family-mono)', color: 'var(--color-text-muted)', fontSize: 'var(--font-size-xs)', marginLeft: 'var(--space-2)' }}>| {boardInfo.codename}</span>
              )}
            </p>
            {/* Row 3: revision */}
            <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
              Revision:
              <span style={{ display: 'inline-block', width: 'var(--space-3)' }} />
              <span style={{ fontFamily: 'var(--font-family-mono)', fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-primary)' }}>
                {formatHwVersion(device.hw_version)}
              </span>
            </p>
          </>
        )}
      </div>

      {/* NVS profile toggle */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', marginBottom: 'var(--space-3)' }}>
        <div style={{ display: 'flex', borderRadius: 'var(--radius-md)', border: '1px solid var(--color-border)', overflow: 'hidden' }}>
          {[['current', 'Current Gen NVS'], ['legacy', 'Legacy NVS']].map(([val, lbl]) => (
            <button
              key={val}
              type="button"
              onClick={() => setNvsProfile(val)}
              style={{
                padding: '2px 10px',
                fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-medium)',
                cursor: 'pointer', border: 'none',
                backgroundColor: nvsProfile === val ? 'var(--color-primary)' : 'var(--color-bg-elevated)',
                color: nvsProfile === val ? 'var(--color-bg-base)' : 'var(--color-text-muted)',
                transition: 'background-color 0.15s, color 0.15s',
              }}
            >{lbl}</button>
          ))}
        </div>
      </div>

      {!webSerialAvailable && (
        <div style={{ padding: 'var(--space-3)', borderRadius: 'var(--radius-md)', marginBottom: 'var(--space-3)', backgroundColor: 'var(--color-warning-bg)', border: '1px solid var(--color-warning)', color: 'var(--color-warning)', fontSize: 'var(--font-size-sm)' }}>
          Web Serial API not available. Use Chrome or Edge on a desktop system.
        </div>
      )}

      {error && <div style={{ marginBottom: 'var(--space-3)' }}><ErrorBox msg={error} /></div>}

      {/* Progress bars — always visible, idle at 0% */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-3)', marginBottom: 'var(--space-3)' }}>
        <div style={{ display: 'flex', gap: 'var(--space-4)' }}>
          <ProgressBar flex label="Bootloader (0x1000)"      percent={blProgress} />
          <ProgressBar flex label="Partition Table (0x8000)" percent={partProgress} />
        </div>
        <ProgressBar label="NVS (0x9000)"       percent={nvsProgress} />
        <ProgressBar label="Firmware (0x10000)" percent={fwProgress} />
      </div>

      {/* Spacer */}
      <div style={{ flex: 1 }} />

      {/* Bottom bar */}
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 'var(--space-3)', paddingTop: 'var(--space-3)', marginTop: 'var(--space-2)' }}>
        {/* Left: status hint */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {portConnected && !flashing && !done && log.length === 0 && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)', fontSize: 'var(--font-size-xs)', color: '#4dd6c8' }}>
              <span style={{ width: 7, height: 7, borderRadius: '50%', backgroundColor: '#22c55e', display: 'inline-block' }} />
              Ready to flash.
            </div>
          )}
          {flashing && (
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>Flashing — do not disconnect…</p>
          )}
          <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', opacity: 0.5 }}>
            NVS 0x9000 · FW 0x10000 · {FLASH_BAUD} baud
          </p>
        </div>

        {/* Right: action buttons */}
        {!busy && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
            {!portConnected && (
              <Button variant="secondary" onClick={handleConnectPort} disabled={!webSerialAvailable}>
                Select COM Port
              </Button>
            )}
            {portConnected && done && (
              <Button variant="ghost" onClick={handleStartFlash}>Flash Again</Button>
            )}
            {done && (
              <Button variant="primary" onClick={onFlashed}>
                Proceed to Verify <Icon name="arrow-right" size={14} />
              </Button>
            )}
            {portConnected && !done && (
              <Button variant="primary" onClick={handleStartFlash}>
                Start Flashing
              </Button>
            )}
          </div>
        )}
        {busy && (
          <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
            <Spinner size="sm" />
          </div>
        )}
      </div>
    </div>
  )

  // ── Right panel: flash output log ─────────────────────────────────────────
  const FlashOutputPanel = (
    <div style={{
      borderRadius: 'var(--radius-lg)',
      border: '1px solid var(--color-border)',
      overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
      height: 320,
      alignSelf: 'start',
      position: 'sticky', top: 0,
    }}>
      <div style={{
        padding: '8px 12px',
        fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)',
        textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)',
        color: 'var(--color-text-muted)',
        backgroundColor: 'var(--color-bg-elevated)',
        borderBottom: '1px solid var(--color-border)',
        flexShrink: 0,
      }}>
        Flash Output
      </div>
      <div style={{
        flex: 1, minHeight: 0,
        padding: 'var(--space-3)',
        overflowY: 'auto',
        fontFamily: 'var(--font-family-mono)',
        fontSize: 'var(--font-size-xs)',
        backgroundColor: 'var(--color-bg-base)',
        color: 'var(--color-text-secondary)',
        lineHeight: 1.6,
      }}>
        {log.length === 0
          ? <span style={{ color: 'var(--color-text-muted)', opacity: 0.5 }}>{flashing ? 'Connecting…' : 'Output will appear here once flashing starts.'}</span>
          : log.map((line, i) => <div key={i}>{line}</div>)
        }
        <div ref={logEndRef} />
      </div>
    </div>
  )

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
      {/* Info panel (left) | Flash output (right) */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-3)', alignItems: 'stretch' }}>
        {InfoPanel}
        {FlashOutputPanel}
      </div>
    </div>
  )
}

// ─── Step 3: Verify ───────────────────────────────────────────────────────────

function StepVerify({ device, onVerified }) {
  const [polling, setPolling]             = useState(false)
  const [timedOut, setTimedOut]           = useState(false)
  const [verified, setVerified]           = useState(false)
  const [heartbeatData, setHeartbeatData] = useState(null)
  const [error, setError]                 = useState('')
  const intervalRef = useRef(null)
  const timeoutRef  = useRef(null)

  const startPolling = useCallback(() => {
    if (polling) return
    setPolling(true); setTimedOut(false); setError('')

    const startTime = Date.now()

    intervalRef.current = setInterval(async () => {
      try {
        const hbData = await api.get(`/mqtt/heartbeats/${device.serial_number}?limit=1&offset=0`)
        if (hbData.heartbeats?.length > 0) {
          const latest     = hbData.heartbeats[0]
          const receivedMs = latest.received_at
            ? Date.parse(latest.received_at.replace(' ', 'T') + 'Z') : NaN
          if (!isNaN(receivedMs) && receivedMs > startTime) {
            clearInterval(intervalRef.current)
            clearTimeout(timeoutRef.current)
            try {
              await api.request(`/manufacturing/devices/${device.serial_number}/status`, {
                method: 'PATCH',
                body: JSON.stringify({ status: 'provisioned', note: 'Auto-verified via wizard' }),
              })
            } catch (_) {}
            const deviceData = await api.get(`/manufacturing/devices/${device.serial_number}`)
            setHeartbeatData(latest)
            setPolling(false)
            setVerified(true)
            onVerified({ ...deviceData, mfg_status: 'provisioned' })
          }
        }
      } catch (err) { setError(err.message) }
    }, VERIFY_POLL_MS)

    timeoutRef.current = setTimeout(() => {
      clearInterval(intervalRef.current)
      setPolling(false); setTimedOut(true)
    }, VERIFY_TIMEOUT_MS)
  }, [polling, device.serial_number, onVerified])

  useEffect(() => {
    startPolling()
    return () => { clearInterval(intervalRef.current); clearTimeout(timeoutRef.current) }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  const stopPolling = () => {
    clearInterval(intervalRef.current); clearTimeout(timeoutRef.current); setPolling(false)
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
      <div style={{ padding: 'var(--space-5)', backgroundColor: 'var(--color-bg-elevated)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-lg)', minHeight: 280 }}>
        <p style={{ fontSize: 'var(--font-size-xs)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-4)' }}>
          Waiting for Device
        </p>

        {polling && !verified && (
          <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: 'var(--space-6) 0', gap: 'var(--space-4)' }}>
            <Spinner size="lg" />
            <p style={{ fontSize: 'var(--font-size-sm)', textAlign: 'center', color: 'var(--color-text-secondary)' }}>
              Waiting for device to connect…<br />
              <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
                Power cycle the device and ensure it can reach the MQTT broker.
              </span>
            </p>
            <Button variant="ghost" onClick={stopPolling}>Stop</Button>
          </div>
        )}

        {verified && heartbeatData && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
              <div style={{ width: 32, height: 32, borderRadius: '50%', backgroundColor: '#0a2e2a', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Icon name="check" size={16} color="#4dd6c8" />
              </div>
              <p style={{ fontSize: 'var(--font-size-sm)', fontWeight: 'var(--font-weight-semibold)', color: '#4dd6c8' }}>Device is live!</p>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--space-3)', padding: 'var(--space-3)', backgroundColor: 'var(--color-bg-base)', borderRadius: 'var(--radius-md)' }}>
              <InfoCell label="Firmware"   value={heartbeatData.firmware_version || '—'} />
              <InfoCell label="IP Address" value={heartbeatData.ip_address || '—'} />
              <InfoCell label="Uptime"     value={heartbeatData.uptime_display || '—'} />
              <InfoCell label="Gateway"    value={heartbeatData.gateway || '—'} />
            </div>
          </div>
        )}

        {timedOut && !verified && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)', paddingTop: 'var(--space-4)' }}>
            <div style={{ padding: 'var(--space-3)', borderRadius: 'var(--radius-md)', backgroundColor: 'var(--color-warning-bg)', border: '1px solid var(--color-warning)', color: 'var(--color-warning)', fontSize: 'var(--font-size-sm)', textAlign: 'center' }}>
              Timed out after {VERIFY_TIMEOUT_MS / 1000}s. Check WiFi credentials and MQTT broker connectivity.
            </div>
            <Button variant="primary" onClick={startPolling}>Retry Verification</Button>
          </div>
        )}

        {error && !timedOut && !verified && (
          <div style={{ marginTop: 'var(--space-3)' }}><ErrorBox msg={`Poll error (will retry): ${error}`} /></div>
        )}
      </div>
      <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>
        Polling every {VERIFY_POLL_MS / 1000}s · timeout {VERIFY_TIMEOUT_MS / 1000}s
      </p>
    </div>
  )
}

// ─── Step 4: Done ─────────────────────────────────────────────────────────────

function StepDone({ device, onProvisionNext }) {
  const navigate = useNavigate()
  const [showSerialLogs, setShowSerialLogs] = useState(false)

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-4)' }}>
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: 'var(--space-6) var(--space-4)', gap: 'var(--space-4)' }}>
        <div style={{ width: 64, height: 64, borderRadius: '50%', backgroundColor: '#0a2e2a', border: '2px solid #4dd6c8', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 0 24px rgba(77,214,200,0.3)' }}>
          <Icon name="check" size={28} color="#4dd6c8" />
        </div>
        <div style={{ textAlign: 'center' }}>
          <h3 style={{ fontSize: 'var(--font-size-lg)', fontFamily: 'var(--font-family-display)', fontWeight: 'var(--font-weight-bold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-1)' }}>
            Device Provisioned
          </h3>
          <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
            {device?.serial_number} is live.
          </p>
        </div>
      </div>

      {/* Device summary grid */}
      <div style={{ borderRadius: 'var(--radius-lg)', overflow: 'hidden', border: '1px solid var(--color-border)', backgroundColor: 'var(--color-bg-elevated)' }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', borderBottom: '1px solid var(--color-border)' }}>
          <div style={{ padding: 'var(--space-4)', borderRight: '1px solid var(--color-border)' }}>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-1)' }}>Serial Number</p>
            <p style={{ fontSize: 'var(--font-size-sm)', fontFamily: 'var(--font-family-mono)', color: 'var(--color-text-primary)' }}>{device?.serial_number}</p>
          </div>
          <div style={{ padding: 'var(--space-4)', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-1)' }}>Status</p>
            <StatusBadge variant={STATUS_VARIANT[device?.mfg_status] || 'neutral'}>{device?.mfg_status}</StatusBadge>
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr' }}>
          <div style={{ padding: 'var(--space-4)', borderRight: '1px solid var(--color-border)' }}>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-1)' }}>Board Type</p>
            <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-primary)' }}>{BOARD_TYPE_LABELS[device?.hw_type] || device?.hw_type}</p>
          </div>
          <div style={{ padding: 'var(--space-4)', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
            <p style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)', textTransform: 'uppercase', letterSpacing: 'var(--tracking-wide)', marginBottom: 'var(--space-1)' }}>HW Version</p>
            <p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-primary)' }}>{formatHwVersion(device?.hw_version)}</p>
          </div>
        </div>
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-3)' }}>
        <Button variant="primary" onClick={onProvisionNext}>
          Provision Next Device
        </Button>
        <Button variant="secondary" onClick={() => navigate(`/manufacturing/devices/${device?.serial_number}`)}>
          View in Inventory
        </Button>
        <Button variant="ghost" onClick={() => setShowSerialLogs(true)}>
          View Serial Logs
        </Button>
      </div>

      <SerialLogModal open={showSerialLogs} onClose={() => setShowSerialLogs(false)} logs={[]} />
    </div>
  )
}

// ─── Main Wizard ──────────────────────────────────────────────────────────────
// Steps: 0=Mode  1=Select/Create device  2=Flash  3=Verify  4=Done

export default function ProvisioningWizard() {
  const navigate        = useNavigate()
  const [searchParams]  = useSearchParams()
  const preloadSn       = searchParams.get('sn') || ''

  const [step, setStep]                       = useState(preloadSn ? 1 : 0)
  const [mode, setMode]                       = useState(preloadSn ? 'existing' : null)
  const [device, setDevice]                   = useState(null)
  const [bespokeOverride, setBespokeOverride] = useState(null)
  const createdSnRef                          = useRef(null)

  const handleModePick = (m) => { setMode(m); setStep(1) }

  const handleDeviceSelected = (d, bespoke = null) => {
    setBespokeOverride(bespoke)
    setDevice(d)
    setStep(2)
  }

  const handleCreatedSn = (sn) => { createdSnRef.current = sn }

  const handleFlashed = () => { setStep(3) }

  const handleVerified = (updatedDevice) => { setDevice(updatedDevice); setStep(4) }

  const handleProvisionNext = () => {
    setStep(0); setMode(null); setDevice(null); setBespokeOverride(null); createdSnRef.current = null
  }

  return (
    <div className="page-wrapper page-wrapper--centered" style={{ '--page-content-max-width': step === 2 ? '1200px' : 'var(--content-max-width-md)' }}>
      <PageHeader title="Provisioning Wizard" subtitle="Flash firmware to a Bell Systems board via WebSerial">
        <Button variant="ghost" onClick={() => navigate('/manufacturing')}>
          <Icon name="arrow-left" size={14} />
          Back to Inventory
        </Button>
      </PageHeader>

      {/* Step indicator */}
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <StepIndicator current={step + 1} />
      </div>

      {/* Step content — floating on page background, no card wrapper */}
      <div style={{ paddingTop: 'var(--space-2)' }}>
        {step === 0 && <StepMode onPick={handleModePick} />}

        {step === 1 && (
          <StepSelectDevice
            mode={mode}
            preloadSn={preloadSn}
            onSelected={handleDeviceSelected}
            onCreatedSn={handleCreatedSn}
          />
        )}

        {step === 2 && device && (
          <StepFlash
            device={device}
            bespokeOverride={bespokeOverride}
            onFlashed={handleFlashed}
          />
        )}

        {step === 3 && device && (
          <StepVerify device={device} onVerified={handleVerified} />
        )}

        {step === 4 && device && (
          <StepDone device={device} onProvisionNext={handleProvisionNext} />
        )}
      </div>
    </div>
  )
}
