import { useEffect, useState, useCallback, useRef } from 'react';
import { useParams, useLocation, Link } from 'react-router-dom';
import { SLIDE_TEMPLATES, SLIDE_TEMPLATE_DRAFTS, SLIDE_TEMPLATE_RICH_DEFAULTS, getTemplate } from '../data/slideTemplates.js';
import GlitchPlayerPanel from '../components/GlitchPlayerPanel.jsx';
import ErrorBoundary from '../components/ErrorBoundary.jsx';
import SlideThumbnail from '../components/SlideThumbnail.jsx';

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

function parseManuscriptMarkers(text) {
  if (!text) return [];
  const found = [];
  const re = /\[S(\d+)\]/g;
  let m;
  while ((m = re.exec(text)) !== null) found.push(parseInt(m[1], 10));
  return [...new Set(found)].sort((a, b) => a - b).map(n => `S${n}`);
}

function moveItem(arr, fromIdx, toIdx) {
  const next = [...arr];
  const [item] = next.splice(fromIdx, 1);
  next.splice(toIdx, 0, item);
  return next;
}

function decodeJwt(token) {
  try {
    const payload = token.split('.')[1];
    return JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
  } catch {
    return null;
  }
}

// ─── TemplatePickerModal ──────────────────────────────────────────────────────

const TEMPLATE_GROUPS = [
  { label: 'SQUARE  (1:1 inset)', mode: 'square' },
  { label: 'FULLSCREEN  (16:9 / 1:2)', mode: 'fullscreen' },
];

function TemplatePickerModal({ onPick, onClose, title = 'Choose a Slide Template', videoId, initialHeader = '' }) {
  const [previewHeader, setPreviewHeader] = useState(initialHeader);

  useEffect(() => {
    const h = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onClose]);

  function buildPreviewSlide(tmpl) {
    const richDefaults = SLIDE_TEMPLATE_RICH_DEFAULTS[tmpl.componentKey] || {};
    const previewProps = { ...richDefaults };
    const text = previewHeader.trim() || 'Header';
    if ('header' in richDefaults) previewProps.header = text;
    else if ('eyebrow' in richDefaults) previewProps.eyebrow = text;
    if ('title' in richDefaults) previewProps.title = text;
    return {
      id: `preview-${tmpl.componentKey}-${tmpl.displayMode}`,
      display_mode: tmpl.displayMode,
      component_key: tmpl.componentKey,
      props: previewProps,
    };
  }

  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal modal-xl" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <span className="modal-title">{title}</span>
          <button onClick={onClose} className="btn-icon">✕</button>
        </div>

        {/* Preview text input */}
        <div className="tmpl-preview-bar">
          <label className="tmpl-preview-label">Header</label>
          <input
            className="field-input tmpl-preview-input"
            placeholder="Type a heading to preview across all styles…"
            value={previewHeader}
            onChange={e => setPreviewHeader(e.target.value)}
            autoFocus
          />
        </div>

        <div className="modal-scroll-body">
          {TEMPLATE_GROUPS.map(group => {
            const templates = SLIDE_TEMPLATES.filter(t => t.displayMode === group.mode);
            if (templates.length === 0) return null;
            return (
              <div key={group.mode} className="template-group">
                <div className="template-group-label">{group.label}</div>
                <div className={`template-grid template-grid--${group.mode}`}>
                  {templates.map(tmpl => (
                    <div
                      key={`${tmpl.componentKey}:${tmpl.displayMode}`}
                      onClick={() => onPick(tmpl, previewHeader)}
                      className="template-card"
                    >
                      <div className={`tmpl-thumbnail tmpl-thumbnail--${tmpl.displayMode}`}>
                        <SlideThumbnail
                          videoId={videoId || 'preview'}
                          slide={buildPreviewSlide(tmpl)}
                          viewportMode="desktop"
                        />
                      </div>
                      <div className="tmpl-label">{tmpl.label}</div>
                      <div className="tmpl-desc">{tmpl.description}</div>
                    </div>
                  ))}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ─── AssetPickerModal (supports multi-select) ─────────────────────────────────

function AssetPickerModal({ type, onPick, onPickMultiple, onClose, multiSelect = false }) {
  const [assets, setAssets]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [q, setQ]             = useState('');
  const [selected, setSelected] = useState(new Set());

  useEffect(() => {
    setLoading(true);
    fetch(`/api/assets?type=${type}${q ? `&q=${encodeURIComponent(q)}` : ''}`)
      .then(r => r.json())
      .then(d => setAssets(d.assets || []))
      .catch(() => setAssets([]))
      .finally(() => setLoading(false));
  }, [type, q]);

  useEffect(() => {
    const h = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onClose]);

  function toggleSelect(id) {
    setSelected(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      return next;
    });
  }

  function confirmMulti() {
    const picked = assets
      .filter(a => selected.has(a.id))
      .sort((a, b) => a.original_filename.localeCompare(b.original_filename));
    onPickMultiple(picked);
  }

  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-header">
          <span className="modal-title">
            {multiSelect ? `Select ${type} files` : `Pick a ${type}`}
          </span>
          <input placeholder="Search…" value={q} onChange={e => setQ(e.target.value)}
                 className="field-input" style={{ flex: 1, maxWidth: 240 }} autoFocus />
          <button onClick={onClose} className="btn-icon">✕</button>
        </div>

        {loading && <p className="muted">Loading…</p>}
        {!loading && assets.length === 0 && (
          <p className="muted">No {type} assets. Upload some on the Assets page first.</p>
        )}
        {!loading && assets.length > 0 && (
          <div className="asset-grid">
            {assets.map(a => (
              <div key={a.id}
                   className={`asset-card${selected.has(a.id) ? ' selected' : ''}`}
                   onClick={() => multiSelect ? toggleSelect(a.id) : onPick(a)}
                   title={a.original_filename}>
                {multiSelect && selected.has(a.id) && <div className="check-badge">✓</div>}
                {a.asset_type === 'image' && <img src={a.url} alt="" className="asset-thumb" />}
                {a.asset_type === 'video' && <video src={a.url} className="asset-thumb" muted playsInline />}
                <div className="asset-name">{a.original_filename}</div>
                {a.duration_seconds != null && (
                  <div className="asset-meta">{Number(a.duration_seconds).toFixed(1)}s</div>
                )}
              </div>
            ))}
          </div>
        )}

        {multiSelect && (
          <div style={{ marginTop: 14, display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
            {selected.size > 0 && (
              <span className="muted" style={{ alignSelf: 'center' }}>
                {selected.size} selected — will be sorted by filename
              </span>
            )}
            <button onClick={onClose} className="btn-ghost">Cancel</button>
            <button onClick={confirmMulti} disabled={selected.size === 0} className="btn-primary">
              Add {selected.size > 0 ? selected.size : ''} selected
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

// ─── FieldEditor ──────────────────────────────────────────────────────────────

function FieldEditor({ field, value, onChange, onRequestImagePick }) {
  const toDisplay = v =>
    field.type === 'lines' ? (Array.isArray(v) ? v.join('\n') : (v ?? '')) : (v ?? '');
  const [local, setLocal]   = useState(toDisplay(value));
  const timerRef            = useRef(null);
  useEffect(() => { setLocal(toDisplay(value)); }, [JSON.stringify(value)]);

  function schedule(raw) {
    if (timerRef.current) clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => {
      onChange(field.type === 'lines' ? raw.split('\n').filter(l => l.trim()) : raw);
    }, 500);
  }
  function handleChange(e) { setLocal(e.target.value); schedule(e.target.value); }
  function handleBlur() {
    if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; }
    onChange(field.type === 'lines' ? local.split('\n').filter(l => l.trim()) : local);
  }

  if (field.type === 'image') return (
    <div className="field-row">
      <label className="field-label">{field.label}</label>
      <div style={{ display: 'flex', gap: 6, flex: 1 }}>
        <input value={local} onChange={handleChange} onBlur={handleBlur}
               placeholder={field.placeholder || 'https://…'}
               className="field-input" style={{ flex: 1 }} />
        <button onClick={onRequestImagePick} className="btn-pick">Pick from library</button>
      </div>
    </div>
  );
  if (field.type === 'latex') return (
    <div className="field-row">
      <label className="field-label">{field.label}</label>
      <div style={{ flex: 1 }}>
        <textarea value={local} onChange={handleChange} onBlur={handleBlur}
                  placeholder={field.placeholder} rows={3}
                  className="field-input full resize-v mono" />
        {field.description && <div className="muted" style={{ marginTop: 4 }}>{field.description}</div>}
      </div>
    </div>
  );
  if (field.type === 'textarea' || field.type === 'lines') return (
    <div className="field-row">
      <label className="field-label">{field.label}</label>
      <div style={{ flex: 1 }}>
        <textarea value={local} onChange={handleChange} onBlur={handleBlur}
                  placeholder={field.placeholder} rows={field.type === 'lines' ? 5 : 3}
                  className="field-input full resize-v" />
        {field.description && <div className="muted" style={{ marginTop: 4 }}>{field.description}</div>}
      </div>
    </div>
  );
  return (
    <div className="field-row">
      <label className="field-label">{field.label}</label>
      <input value={local} onChange={handleChange} onBlur={handleBlur}
             placeholder={field.placeholder}
             className="field-input"
             type={field.type === 'number' ? 'number' : 'text'} />
    </div>
  );
}

// ─── SlideStripItem ───────────────────────────────────────────────────────────

function SlideStripItem({ videoId, slide, selected, onSelect, onDelete }) {
  const [confirmDel, setConfirmDel] = useState(false);

  function handleDeleteClick(e) {
    e.stopPropagation();
    setConfirmDel(true);
  }

  function handleConfirm(e) {
    e.stopPropagation();
    onDelete(slide.id);
  }

  function handleCancel(e) {
    e.stopPropagation();
    setConfirmDel(false);
  }

  return (
    <div
      className={`strip-item${selected ? ' strip-item--selected' : ''}`}
      onClick={onSelect}
    >
      <SlideThumbnail videoId={videoId} slide={slide} viewportMode="desktop" />
      <div className="strip-info">
        <span className="strip-num">{slide.slide_order}</span>
        <span className="strip-id">{slide.gnommo_slide_id}</span>
        <span className="strip-type">{slide.component_key || '—'}</span>
        {!confirmDel ? (
          <button className="strip-delete" onClick={handleDeleteClick} title="Delete slide">✕</button>
        ) : (
          <span className="strip-confirm">
            <button className="strip-confirm-yes" onClick={handleConfirm}>DEL</button>
            <button className="strip-confirm-no"  onClick={handleCancel}>NO</button>
          </span>
        )}
      </div>
    </div>
  );
}

// ─── ProposalCard ─────────────────────────────────────────────────────────────

function ProposalCard({ candidate, slide, authUser, onDelete, onPromote }) {
  const [confirmDel, setConfirmDel] = useState(false);
  const isOwner = authUser && authUser.sub === candidate.proposed_by;
  const isEditor = authUser && ['editor', 'admin'].includes(authUser.role);
  const previewSlide = {
    id: `proposal-${candidate.id}`,
    display_mode: candidate.display_mode || slide.display_mode,
    component_key: candidate.component_key,
    props: candidate.props || {},
  };

  return (
    <div className="proposal-card">
      <div className="proposal-card__thumb">
        <SlideThumbnail videoId={slide.video_id} slide={previewSlide} viewportMode="desktop" />
      </div>
      <div className="proposal-card__meta">
        <div className="proposal-card__by">{candidate.proposed_by_name || candidate.proposed_by}</div>
      </div>
      <div className="proposal-card__actions">
        {isEditor && (
          <button className="btn-primary" style={{ fontSize: 7, padding: '3px 8px' }}
                  onClick={() => onPromote(candidate.id)}>
            Promote
          </button>
        )}
        {(isOwner || isEditor) && !confirmDel && (
          <button className="btn-delete" onClick={() => setConfirmDel(true)}>✕</button>
        )}
        {confirmDel && (
          <span className="confirm-row">
            <button className="btn-confirm-yes" onClick={() => onDelete(candidate.id)}>Del</button>
            <button className="btn-confirm-no" onClick={() => setConfirmDel(false)}>No</button>
          </span>
        )}
      </div>
    </div>
  );
}

// ─── SlideCard (editor panel for selected slide) ───────────────────────────────

function SlideCard({
  slide, onDelete, onSave, onPickImage, imagePickResult, onOpenTypePicker,
  candidates, authToken, authUser, onPropose, onUpdateCandidate, onDeleteCandidate, onPromoteCandidate,
}) {
  const template            = getTemplate(slide.component_key);
  const [localProps, setLocalProps] = useState(slide.props || {});
  const [notes, setNotes]   = useState(slide.presenter_notes ?? '');
  const [start, setStart]   = useState(slide.start_time_sec ?? '');
  const [end, setEnd]       = useState(slide.end_time_sec ?? '');
  const [status, setStatus] = useState('idle'); // idle | saving | saved | error
  const [confirmDel, setConfirmDel] = useState(false);

  // Proposal form state
  const [proposeOpen, setProposeOpen] = useState(false);
  const [proposeProps, setProposeProps] = useState({});
  const [proposeStatus, setProposeStatus] = useState('idle'); // idle | saving | error

  const ownCandidate = candidates?.find(c => authUser && c.proposed_by === authUser.sub);

  useEffect(() => {
    if (proposeOpen) {
      // Pre-fill with own candidate's props if editing, else principal's props
      setProposeProps(ownCandidate ? (ownCandidate.props || {}) : (slide.props || {}));
    }
  }, [proposeOpen, slide.id]);

  // Pending updates waiting to be flushed. Coalesces rapid field changes into one request.
  const pendingRef = useRef({});
  const saveTimer  = useRef(null);
  // AbortController for the current in-flight request.
  const abortRef   = useRef(null);

  useEffect(() => { setNotes(slide.presenter_notes ?? ''); }, [slide.presenter_notes]);
  useEffect(() => { setLocalProps(slide.props || {}); pendingRef.current = {}; }, [slide.id]);
  useEffect(() => {
    if (imagePickResult?.slideId === slide.id) {
      const { fieldName, url } = imagePickResult;
      setLocalProps(prev => {
        const up = { ...prev, [fieldName]: url };
        scheduleSave({ props: up });
        return up;
      });
    }
  }, [imagePickResult]);

  // Schedule a save after 400ms of inactivity. Merges new updates into pending.
  function scheduleSave(updates) {
    pendingRef.current = { ...pendingRef.current, ...updates };
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(flushSave, 400);
  }

  async function flushSave() {
    const updates = pendingRef.current;
    pendingRef.current = {};
    if (Object.keys(updates).length === 0) return;

    // Cancel any previous in-flight request — the new one carries all pending changes.
    if (abortRef.current) abortRef.current.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    setStatus('saving');
    try {
      await onSave(slide.id, updates, controller.signal);
      setStatus('saved');
      setTimeout(() => setStatus('idle'), 1800);
    } catch (err) {
      if (err.name === 'AbortError') return; // superseded by a newer save — stay in current state
      setStatus('error');
    }
  }

  // Flush immediately (no delay) — used for blur events and explicit actions.
  async function flushNow(extraUpdates = {}) {
    if (saveTimer.current) { clearTimeout(saveTimer.current); saveTimer.current = null; }
    const updates = { ...pendingRef.current, ...extraUpdates };
    pendingRef.current = {};
    if (Object.keys(updates).length === 0) return;

    if (abortRef.current) abortRef.current.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    setStatus('saving');
    try {
      await onSave(slide.id, updates, controller.signal);
      setStatus('saved');
      setTimeout(() => setStatus('idle'), 1800);
    } catch (err) {
      if (err.name === 'AbortError') return;
      setStatus('error');
    }
  }

  function handleFieldChange(name, val) {
    const up = { ...localProps, [name]: val };
    setLocalProps(up);
    scheduleSave({ props: up });
  }

  return (
    <div className="slide-editor-panel">

      {/* ── Title bar ──────────────────────────────────────────── */}
      <div className="slide-editor-title">
        <span className="slide-num">{slide.slide_order}</span>
        <span className="slide-badge">{slide.gnommo_slide_id}</span>
        <span className="slide-badge">{slide.component_key || '?'}</span>
        <div style={{ flex: 1 }} />
        {status === 'saving' && <span className="saving-dot">saving…</span>}
        {status === 'saved'  && <span className="saved-dot">✓</span>}
        {status === 'error'  && (
          <button className="save-error-btn" onClick={() => flushNow()}>
            SAVE FAILED — retry
          </button>
        )}
        {!confirmDel ? (
          <button className="btn-delete" onClick={() => setConfirmDel(true)}>✕</button>
        ) : (
          <span className="confirm-row">
            <span className="confirm-text">Delete?</span>
            <button className="btn-confirm-yes" onClick={() => onDelete(slide.id)}>Yes</button>
            <button className="btn-confirm-no"  onClick={() => setConfirmDel(false)}>No</button>
          </span>
        )}
      </div>

      {/* ── Type selector ──────────────────────────────────────── */}
      <div className="field-row" style={{ marginBottom: 8 }}>
        <label className="field-label">Type</label>
        <button className="btn-change-type" onClick={onOpenTypePicker}>
          {template?.label ?? slide.component_key ?? '?'} · Change
        </button>
      </div>


      <hr className="divider" />

      <div className="field-row">
        <label className="field-label">Narration</label>
        <textarea value={notes}
          onChange={e => { setNotes(e.target.value); scheduleSave({ presenter_notes: e.target.value }); }}
          onBlur={e => flushNow({ presenter_notes: e.target.value })}
          placeholder="Spoken narration for this slide…" rows={4}
          className="field-input full resize-v" />
      </div>

      {template?.fields.length > 0 && (
        <><hr className="divider" />
        {template.fields.map(field => (
          <FieldEditor key={field.name} field={field} value={localProps[field.name] ?? ''}
            onChange={val => handleFieldChange(field.name, val)}
            onRequestImagePick={field.type === 'image' ? () => onPickImage(slide.id, field.name) : undefined} />
        ))}</>
      )}

      <hr className="divider" />
      <div style={{ display: 'flex', gap: 16, alignItems: 'center', marginBottom: 14 }}>
        <label className="field-label inline">Timing</label>
        <input type="number" step="0.1" min="0" value={start}
               onChange={e => setStart(e.target.value)}
               onBlur={e => flushNow({ start_time_sec: e.target.value === '' ? null : parseFloat(e.target.value) })}
               className="field-input" style={{ width: 82 }} placeholder="Start" />
        <span className="muted">→</span>
        <input type="number" step="0.1" min="0" value={end}
               onChange={e => setEnd(e.target.value)}
               onBlur={e => flushNow({ end_time_sec: e.target.value === '' ? null : parseFloat(e.target.value) })}
               className="field-input" style={{ width: 82 }} placeholder="End" />
        <span className="muted">seconds</span>
      </div>

      {/* ── Proposals ──────────────────────────────────────────── */}
      <hr className="divider" />
      <div className="proposals-section">
        <div className="proposals-header">
          <span className="sub-label">PROPOSALS  ({candidates?.length ?? 0})</span>
        </div>

        {candidates?.length > 0 && (
          <div className="proposals-grid">
            {candidates.map(c => (
              <ProposalCard
                key={c.id}
                candidate={c}
                slide={slide}
                authUser={authUser}
                onDelete={id => onDeleteCandidate(slide.id, id)}
                onPromote={id => onPromoteCandidate(slide.id, id)}
              />
            ))}
          </div>
        )}

        {authUser && !ownCandidate && !proposeOpen && (
          <button className="btn-add" style={{ marginTop: 8 }}
                  onClick={() => setProposeOpen(true)}>
            + Propose Alternate
          </button>
        )}
        {authUser && ownCandidate && !proposeOpen && (
          <button className="btn-ghost-sm" style={{ marginTop: 8 }}
                  onClick={() => setProposeOpen(true)}>
            ✎ Edit My Proposal
          </button>
        )}

        {proposeOpen && template && (
          <div className="proposal-form">
            <div className="proposal-form__title">
              {ownCandidate ? 'Edit My Proposal' : 'Propose Alternate'}
            </div>
            {template.fields.map(field => (
              <FieldEditor
                key={field.name}
                field={field}
                value={proposeProps[field.name] ?? ''}
                onChange={val => setProposeProps(prev => ({ ...prev, [field.name]: val }))}
                onRequestImagePick={undefined}
              />
            ))}
            {proposeStatus === 'error' && (
              <div className="error-text" style={{ marginTop: 4 }}>Save failed — try again</div>
            )}
            <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
              <button
                className="btn-primary"
                disabled={proposeStatus === 'saving'}
                onClick={async () => {
                  setProposeStatus('saving');
                  try {
                    if (ownCandidate) {
                      await onUpdateCandidate(slide.id, ownCandidate.id, {
                        component_key: slide.component_key,
                        display_mode: slide.display_mode,
                        props: proposeProps,
                      });
                    } else {
                      await onPropose(slide.id, {
                        component_key: slide.component_key,
                        display_mode: slide.display_mode,
                        props: proposeProps,
                      });
                    }
                    setProposeStatus('idle');
                    setProposeOpen(false);
                  } catch {
                    setProposeStatus('error');
                  }
                }}
              >
                {proposeStatus === 'saving' ? 'Saving…' : 'Submit'}
              </button>
              <button className="btn-ghost" onClick={() => { setProposeOpen(false); setProposeStatus('idle'); }}>
                Cancel
              </button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ─── SegmentList ─────────────────────────────────────────────────────────────

function SegmentList({ label, segmentType, segments, videoId, onAddSegments, onDelete, onMove }) {
  const [confirmDel, setConfirmDel] = useState(null);
  const [showPicker, setShowPicker] = useState(false);

  async function handleAdd(selectedAssets) {
    setShowPicker(false);
    if (selectedAssets.length === 0) return;
    const sorted = [...selectedAssets].sort((a, b) =>
      a.original_filename.localeCompare(b.original_filename)
    );
    await onAddSegments(sorted.map(a => a.id), segmentType);
  }

  return (
    <div style={{ marginBottom: 20 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
        <span className="sub-label">{label}</span>
        <button className="btn-add" onClick={() => setShowPicker(true)}>+ Add Videos</button>
      </div>

      {segments.length === 0 && (
        <p className="muted" style={{ paddingLeft: 4 }}>No {label.toLowerCase()} yet.</p>
      )}

      {segments.map((seg, idx) => (
        <div key={seg.id} className="segment-row">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 2, flexShrink: 0 }}>
            <button className="btn-arrow" disabled={idx === 0}
                    onClick={() => onMove(segmentType, idx, idx - 1)}>▲</button>
            <button className="btn-arrow" disabled={idx === segments.length - 1}
                    onClick={() => onMove(segmentType, idx, idx + 1)}>▼</button>
          </div>

          {seg.url && (
            <video src={seg.url} className="segment-thumb" muted playsInline />
          )}

          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="segment-name">{seg.asset_name || seg.source_file || seg.segment_key}</div>
            <div className="muted">
              {seg.segment_key}
              {seg.asset_duration != null && ` · ${Number(seg.asset_duration).toFixed(1)}s`}
            </div>
          </div>

          {confirmDel !== seg.id ? (
            <button className="btn-delete" onClick={() => setConfirmDel(seg.id)} title="Remove">✕</button>
          ) : (
            <span className="confirm-row">
              <span className="confirm-text">Remove?</span>
              <button className="btn-confirm-yes" onClick={() => { onDelete(seg.id); setConfirmDel(null); }}>Yes</button>
              <button className="btn-confirm-no"  onClick={() => setConfirmDel(null)}>No</button>
            </span>
          )}
        </div>
      ))}

      {showPicker && (
        <AssetPickerModal
          type="video"
          multiSelect
          onPickMultiple={handleAdd}
          onClose={() => setShowPicker(false)}
        />
      )}
    </div>
  );
}

// ─── ManuscriptSection ────────────────────────────────────────────────────────

function ManuscriptSection({ slideCount, onImport }) {
  const [text, setText] = useState('');
  const [open, setOpen] = useState(true);
  const markers = parseManuscriptMarkers(text);
  useEffect(() => { if (slideCount > 0) setOpen(false); }, [slideCount > 0]);
  return (
    <section className="section">
      <div className="section-header">
        <h2 className="section-title">Manuscript</h2>
        <button className="btn-ghost-sm" onClick={() => setOpen(o => !o)}>{open ? '▲ collapse' : '▼ expand'}</button>
      </div>
      {open && (
        <>
          <textarea value={text} onChange={e => setText(e.target.value)} rows={10}
            placeholder={
              `Paste your narration script here. Mark slide transitions with [S1], [S2], [S3]…\n\n` +
              `Example:\n[S1]\nWelcome to this course. Today we'll cover…\n\n[S2]\nLet's start with the fundamentals of…`
            }
            className="field-input full resize-v mono" />
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 10 }}>
            {text.trim() && markers.length === 0 && (
              <span className="muted">No [S1]/[S2]… markers found yet.</span>
            )}
            {markers.length > 0 && (
              <>
                <span style={{ color: '#00ffff', fontSize: 8 }}>
                  Found {markers.length} slide{markers.length !== 1 ? 's' : ''}: {markers.join(' · ')}
                </span>
                <button className="btn-primary" onClick={() => onImport(text, markers)}>
                  Create {markers.length} slides →
                </button>
              </>
            )}
          </div>
        </>
      )}
    </section>
  );
}

// ─── VideoEditorPage ──────────────────────────────────────────────────────────

export default function VideoEditorPage() {
  const { id } = useParams();
  const location = useLocation();

  const [video, setVideo]       = useState(null);
  const [slides, setSlides]     = useState([]);
  const [rawSegs, setRawSegs]   = useState([]);
  const [finalSegs, setFinalSegs] = useState([]);
  const [loading, setLoading]   = useState(true);
  const [error, setError]       = useState(null);
  const [saving, setSaving]     = useState(false);
  const [saved, setSaved]       = useState(false);
  const [selectedSlideId, setSelectedSlideId]     = useState(null);
  const [previewSlideId, setPreviewSlideId]       = useState(null);
  const [previewMode, setPreviewMode]             = useState('desktop');
  const [presentationVersion, setPresentationVersion] = useState(0);

  const [templatePickerFor, setTemplatePickerFor] = useState(null);
  const [pendingManuscript, setPendingManuscript]  = useState(null);
  const [imagePickTarget, setImagePickTarget]      = useState(null);
  const [imagePickResult, setImagePickResult]      = useState(null);

  const [titleDraft, setTitleDraft]           = useState('');
  const [statusDraft, setStatusDraft]         = useState('draft');
  const [courseCodeDraft, setCourseCodeDraft] = useState('');

  // Auth
  const [authToken, setAuthToken] = useState(() => localStorage.getItem('gnommoeditor_token'));
  const [authUser, setAuthUser]   = useState(() => {
    const t = localStorage.getItem('gnommoeditor_token');
    return t ? decodeJwt(t) : null;
  });
  const [showTokenInput, setShowTokenInput] = useState(false);
  const [tokenDraft, setTokenDraft]         = useState('');

  // Candidates: Map<slideId, candidate[]>
  const [candidatesBySlideId, setCandidatesBySlideId] = useState(new Map());

  // On mount: pick up ?token= from URL and persist it
  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const urlToken = params.get('token');
    if (urlToken) {
      localStorage.setItem('gnommoeditor_token', urlToken);
      setAuthToken(urlToken);
      setAuthUser(decodeJwt(urlToken));
    }
  }, []);

  function applySegments(all) {
    setRawSegs(all.filter(s => (s.segment_type ?? 'raw') === 'raw'));
    setFinalSegs(all.filter(s => s.segment_type === 'final'));
  }

  const load = useCallback(() => {
    setLoading(true); setError(null);
    fetch(`/api/videos/${id}`)
      .then(r => r.ok ? r.json() : r.json().then(e => Promise.reject(e.error)))
      .then(data => {
        setVideo(data.video); setSlides(data.slides || []);
        applySegments(data.narrationSegments || []);
        setTitleDraft(data.video.title || '');
        setStatusDraft(data.video.status || 'draft');
        setCourseCodeDraft(data.video.course_code || '');
      })
      .catch(err => setError(String(err)))
      .finally(() => setLoading(false));
  }, [id]);

  useEffect(() => { load(); }, [load]);

  async function saveMetadata() {
    setSaving(true); setSaved(false);
    try {
      const res = await fetch(`/api/videos/${id}`, {
        method: 'PUT', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title: titleDraft, status: statusDraft, course_code: courseCodeDraft }),
      });
      if (res.ok) { setSaved(true); setTimeout(() => setSaved(false), 2000); }
    } finally { setSaving(false); }
  }

  // ── Slides ──────────────────────────────────────────────────────────────────

  async function addSlide(template, headerText = '') {
    setTemplatePickerFor(null);
    const draft = SLIDE_TEMPLATE_DRAFTS[template.componentKey];
    const base = draft
      ? { ...draft.props, ...(draft.latexString !== undefined ? { latexString: draft.latexString } : {}) }
      : {};
    const richDefaults = SLIDE_TEMPLATE_RICH_DEFAULTS[template.componentKey] || {};
    const initialProps = { ...base, ...richDefaults };
    if (headerText.trim()) {
      if ('header' in richDefaults) initialProps.header = headerText.trim();
      else if ('eyebrow' in richDefaults) initialProps.eyebrow = headerText.trim();
    }
    const res = await fetch(`/api/videos/${id}/slides`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ component_key: template.componentKey, display_mode: template.displayMode || 'fullscreen', props: initialProps }),
    });
    if (res.ok) {
      const data = await res.json();
      setSlides(prev => [...prev, data.slide]);
      selectSlide(data.slide);
    }
  }

  async function importManuscript(template) {
    if (!pendingManuscript) return;
    setTemplatePickerFor(null);
    const res = await fetch(`/api/videos/${id}/import-manuscript`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ manuscript: pendingManuscript, component_key: template.componentKey, display_mode: template.displayMode || 'fullscreen' }),
    });
    if (res.ok) { const data = await res.json(); setSlides(data.slides); setPendingManuscript(null); }
  }

  function handleTemplatePick(t, headerText = '') {
    if (templatePickerFor === 'add-single') addSlide(t, headerText);
    if (templatePickerFor === 'import')     importManuscript(t);
    if (templatePickerFor === 'change-type') {
      setTemplatePickerFor(null);
      updateSlide(selectedSlideId, { component_key: t.componentKey, display_mode: t.displayMode });
    }
  }

  async function deleteSlide(slideId) {
    await fetch(`/api/slides/${slideId}`, { method: 'DELETE' });
    setSlides(prev => prev.filter(s => s.id !== slideId));
    if (selectedSlideId === slideId) setSelectedSlideId(null);
  }

  async function updateSlide(slideId, updates, signal) {
    const res = await fetch(`/api/slides/${slideId}`, {
      method: 'PUT', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(updates),
      signal,
    });
    if (!res.ok) throw new Error(`Save failed (${res.status})`);
    const data = await res.json();
    setSlides(prev => prev.map(s => s.id === slideId ? data.slide : s));
    setPresentationVersion(v => v + 1);
  }

  function selectSlide(slide) {
    setSelectedSlideId(slide.id);
    setPreviewSlideId(slide.gnommo_slide_id || slide.id);
    fetchCandidates(slide.id);
  }

  async function fetchCandidates(slideId) {
    try {
      const res = await fetch(`/api/slides/${slideId}/candidates`);
      if (!res.ok) return;
      const data = await res.json();
      setCandidatesBySlideId(prev => new Map(prev).set(slideId, data.candidates || []));
    } catch {
      // silently ignore
    }
  }

  async function handlePropose(slideId, body) {
    const res = await fetch(`/api/slides/${slideId}/candidates`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.error || `HTTP ${res.status}`);
    }
    await fetchCandidates(slideId);
  }

  async function handleUpdateCandidate(slideId, candidateId, body) {
    const res = await fetch(`/api/slides/${slideId}/candidates/${candidateId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.error || `HTTP ${res.status}`);
    }
    await fetchCandidates(slideId);
  }

  async function handleDeleteCandidate(slideId, candidateId) {
    const res = await fetch(`/api/slides/${slideId}/candidates/${candidateId}`, {
      method: 'DELETE',
      headers: { 'Authorization': `Bearer ${authToken}` },
    });
    if (!res.ok) return;
    setCandidatesBySlideId(prev => {
      const next = new Map(prev);
      const list = (next.get(slideId) || []).filter(c => c.id !== candidateId);
      next.set(slideId, list);
      return next;
    });
  }

  async function handlePromoteCandidate(slideId, candidateId) {
    const res = await fetch(`/api/slides/${slideId}/candidates/${candidateId}/promote`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${authToken}` },
    });
    if (!res.ok) return;
    const data = await res.json();
    setSlides(prev => prev.map(s => s.id === slideId ? data.slide : s));
    setPresentationVersion(v => v + 1);
    // Candidate was deleted by promote — refresh
    await fetchCandidates(slideId);
  }

  // ── Narration segments ──────────────────────────────────────────────────────

  async function addSegments(assetIds, segmentType) {
    const res = await fetch(`/api/videos/${id}/narration-segments`, {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ asset_ids: assetIds, segment_type: segmentType }),
    });
    if (res.ok) {
      const data = await res.json();
      if (segmentType === 'final') setFinalSegs(prev => [...prev, ...data.segments]);
      else                         setRawSegs(prev => [...prev, ...data.segments]);
    }
  }

  async function deleteSegment(segmentId) {
    await fetch(`/api/videos/${id}/narration-segments/${segmentId}`, { method: 'DELETE' });
    setRawSegs(prev => prev.filter(s => s.id !== segmentId));
    setFinalSegs(prev => prev.filter(s => s.id !== segmentId));
  }

  async function moveSegment(segmentType, fromIdx, toIdx) {
    const setter   = segmentType === 'final' ? setFinalSegs : setRawSegs;
    const current  = segmentType === 'final' ? finalSegs : rawSegs;
    const reordered = moveItem(current, fromIdx, toIdx);
    setter(reordered);
    await fetch(`/api/videos/${id}/narration-segments/reorder`, {
      method: 'PUT', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ids: reordered.map(s => s.id) }),
    });
  }

  // ── Image pick for slide fields ─────────────────────────────────────────────

  async function handleImagePickForSlide(asset) {
    if (!imagePickTarget) return;
    const { slideId, fieldName } = imagePickTarget;
    setImagePickTarget(null);
    setImagePickResult({ slideId, fieldName, url: asset.url });
    const slide = slides.find(s => s.id === slideId);
    const newProps = { ...(slide?.props || {}), [fieldName]: asset.url };
    await updateSlide(slideId, { props: newProps });
  }

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

  if (loading) return <div className="editor-page"><p className="muted">Loading…</p></div>;
  if (error)   return <div className="editor-page"><p className="error-text">Error: {error}</p></div>;
  if (!video)  return null;

  const selectedSlide = slides.find(s => s.id === selectedSlideId) ?? null;

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

      {/* ── Header ───────────────────────────────────────────────────────── */}
      <div className="editor-header">
        <Link to="/" className="back-link">← Videos</Link>
        <input value={titleDraft} onChange={e => setTitleDraft(e.target.value)}
               onKeyDown={e => e.key === 'Enter' && saveMetadata()}
               className="title-input" placeholder="Presentation title…" />
        <select value={statusDraft} onChange={e => setStatusDraft(e.target.value)} className="field-select">
          <option value="draft">draft</option>
          <option value="scripted">scripted</option>
          <option value="complete">complete</option>
        </select>
        <input value={courseCodeDraft} onChange={e => setCourseCodeDraft(e.target.value)}
               className="field-input" style={{ width: 150 }} placeholder="Course code" />
        <button onClick={saveMetadata} disabled={saving} className="btn-primary">
          {saving ? 'Saving…' : saved ? 'Saved ✓' : 'Save'}
        </button>
        <a href={`/api/videos/${id}/export`} target="_blank" rel="noreferrer" className="export-link">Export ↗</a>

        {/* Auth indicator */}
        <div className="auth-indicator" onClick={() => { setShowTokenInput(t => !t); setTokenDraft(''); }}>
          {authUser
            ? <span className="auth-indicator__name">⬤ {authUser.name || authUser.email || authUser.sub}</span>
            : <span className="auth-indicator__guest">Not logged in</span>
          }
        </div>
        {showTokenInput && (
          <div className="auth-token-popup">
            {!authUser && import.meta.env.VITE_GNOMMOWEB_URL && (
              <a
                className="btn-primary"
                style={{ fontSize: 7, textDecoration: 'none', whiteSpace: 'nowrap' }}
                href={`${import.meta.env.VITE_GNOMMOWEB_URL}/auth/token-redirect?returnTo=${encodeURIComponent(window.location.href)}`}
              >
                Login via Glitch University →
              </a>
            )}
            <input
              className="field-input"
              style={{ width: 200, fontSize: 8 }}
              placeholder="…or paste JWT token"
              value={tokenDraft}
              onChange={e => setTokenDraft(e.target.value)}
              autoFocus={!import.meta.env.VITE_GNOMMOWEB_URL}
            />
            <button className="btn-ghost-sm" onClick={() => {
              const t = tokenDraft.trim();
              if (!t) return;
              localStorage.setItem('gnommoeditor_token', t);
              setAuthToken(t);
              setAuthUser(decodeJwt(t));
              setShowTokenInput(false);
            }}>Set</button>
            {authToken && (
              <button className="btn-ghost-sm" onClick={() => {
                localStorage.removeItem('gnommoeditor_token');
                setAuthToken(null); setAuthUser(null); setShowTokenInput(false);
              }}>Log out</button>
            )}
          </div>
        )}
      </div>

      {/* ── Manuscript ───────────────────────────────────────────────────── */}
      <ManuscriptSection
        slideCount={slides.length}
        onImport={(text, markers) => { setPendingManuscript(text); setTemplatePickerFor('import'); }}
      />

      {/* ── Narration ────────────────────────────────────────────────────── */}
      <section className="section">
        <div className="section-header" style={{ marginBottom: 16 }}>
          <h2 className="section-title">Narration</h2>
        </div>

        <SegmentList
          label="Raw Footage"
          segmentType="raw"
          segments={rawSegs}
          videoId={id}
          onAddSegments={addSegments}
          onDelete={deleteSegment}
          onMove={moveSegment}
        />

        <hr className="divider" />

        <SegmentList
          label="Final (for player)"
          segmentType="final"
          segments={finalSegs}
          videoId={id}
          onAddSegments={addSegments}
          onDelete={deleteSegment}
          onMove={moveSegment}
        />
      </section>

      {/* ── Slides ───────────────────────────────────────────────────────── */}
      <section className="section">
        <div className="section-header">
          <h2 className="section-title">Slides ({slides.length})</h2>
          <button onClick={() => setTemplatePickerFor('add-single')} className="btn-add">+ Add Slide</button>
        </div>

        {slides.length === 0 && (
          <p className="muted">Paste a manuscript above to create slides, or click "+ Add Slide".</p>
        )}

        {slides.length > 0 && (
          <div className="slides-pane">

            {/* ── Filmstrip ───────────────────────────────────────── */}
            <div className="slides-filmstrip">
              {slides.map(slide => (
                <SlideStripItem
                  key={slide.id}
                  videoId={id}
                  slide={slide}
                  selected={selectedSlideId === slide.id}
                  onSelect={() => selectSlide(slide)}
                  onDelete={deleteSlide}
                />
              ))}
              <div className="strip-add">
                <button onClick={() => setTemplatePickerFor('add-single')} className="btn-add">+ Add</button>
              </div>
            </div>

            {/* ── Editor panel ────────────────────────────────────── */}
            <div className="slides-editor">

              {/* Player — stays mounted; only targetSlideId changes on selection */}
              <div className="slide-editor-player-host">
                <div className="slide-editor-player-controls">
                  <span className="muted">
                    {selectedSlide
                      ? `${selectedSlide.gnommo_slide_id} · ${selectedSlide.component_key || '?'}`
                      : 'Select a slide'}
                  </span>
                  <div style={{ display: 'flex', gap: 4 }}>
                    <button
                      className={previewMode === 'desktop' ? 'btn-confirm-yes' : 'btn-confirm-no'}
                      onClick={() => setPreviewMode('desktop')}
                    >16:9</button>
                    <button
                      className={previewMode === 'mobile-portrait' ? 'btn-confirm-yes' : 'btn-confirm-no'}
                      onClick={() => setPreviewMode('mobile-portrait')}
                    >1:2</button>
                  </div>
                </div>
                <ErrorBoundary label="player preview">
                  <GlitchPlayerPanel
                    videoId={id}
                    targetSlideId={previewSlideId ?? slides[0]?.gnommo_slide_id}
                    viewportMode={previewMode}
                    refreshKey={presentationVersion}
                  />
                </ErrorBoundary>
              </div>

              {/* Slide edit fields */}
              {selectedSlide ? (
                <SlideCard
                  key={selectedSlide.id}
                  slide={selectedSlide}
                  onDelete={deleteSlide}
                  onSave={updateSlide}
                  onPickImage={(slideId, fieldName) => setImagePickTarget({ slideId, fieldName })}
                  imagePickResult={imagePickResult}
                  onOpenTypePicker={() => setTemplatePickerFor('change-type')}
                  candidates={candidatesBySlideId.get(selectedSlide.id) || []}
                  authToken={authToken}
                  authUser={authUser}
                  onPropose={handlePropose}
                  onUpdateCandidate={handleUpdateCandidate}
                  onDeleteCandidate={handleDeleteCandidate}
                  onPromoteCandidate={handlePromoteCandidate}
                />
              ) : (
                <p className="slide-editor-empty">Select a slide to edit.</p>
              )}
            </div>

          </div>
        )}
      </section>


{/* ── Modals ───────────────────────────────────────────────────────── */}
      {templatePickerFor && (
        <TemplatePickerModal
          onPick={handleTemplatePick}
          onClose={() => { setTemplatePickerFor(null); setPendingManuscript(null); }}
          title={
            templatePickerFor === 'import'
              ? `Choose template for ${pendingManuscript ? parseManuscriptMarkers(pendingManuscript).length : ''} imported slides`
              : templatePickerFor === 'change-type'
              ? 'Change Slide Type'
              : 'Choose a Slide Template'
          }
          videoId={id}
          initialHeader={
            templatePickerFor === 'change-type'
              ? (selectedSlide?.props?.header || selectedSlide?.props?.eyebrow || '')
              : ''
          }
        />
      )}
      {imagePickTarget && (
        <AssetPickerModal type="image" onPick={handleImagePickForSlide}
                          onClose={() => setImagePickTarget(null)} />
      )}
    </div>
  );
}
