// ============================================================
// REDZ ENGINE — Frontend (app.jsx)
// Single-file React app. Loaded by index.html via Babel.
// All 8 screens. Talks to the Node/Express API at /api.
// ============================================================

const { useState, useEffect, useRef, useCallback } = React;

// ─── API helper ─────────────────────────────────────────────
const API = '/api';
let authUser = null;
let onAuthFail = null;

async function api(path, opts = {}) {
  const res = await fetch(API + path, {
    credentials: 'include',
    headers: opts.body instanceof FormData ? {} : { 'Content-Type': 'application/json' },
    ...opts,
    body: opts.body instanceof FormData ? opts.body : (opts.body ? JSON.stringify(opts.body) : undefined),
  });
  if (res.status === 401) {
    if (onAuthFail) onAuthFail();
    throw new Error('Session expired — please log in again');
  }
  if (!res.ok) {
    let data = {};
    try { data = await res.json(); } catch (e) {}
    const err = new Error(data.error || 'Request failed');
    if (data.errors) err.errors = data.errors;
    throw err;
  }
  if (res.headers.get('content-type')?.includes('spreadsheet') || res.headers.get('content-type')?.includes('octet-stream')) {
    return res.blob();
  }
  return res.json();
}

function uploadFormData(path, formData, { onProgress } = {}) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('POST', API + path);
    xhr.withCredentials = true;
    xhr.upload.addEventListener('progress', (event) => {
      if (!onProgress) return;
      if (event.lengthComputable && event.total > 0) {
        onProgress(Math.min(100, Math.round((event.loaded / event.total) * 100)));
      }
    });
    xhr.addEventListener('load', () => {
      let data = null;
      try {
        data = xhr.responseText ? JSON.parse(xhr.responseText) : null;
      } catch (err) {
        reject(new Error('Upload failed'));
        return;
      }
      if (xhr.status === 401) {
        if (onAuthFail) onAuthFail();
        reject(new Error('Session expired — please log in again'));
        return;
      }
      if (xhr.status < 200 || xhr.status >= 300) {
        reject(new Error(data?.error || 'Upload failed'));
        return;
      }
      resolve(data);
    });
    xhr.addEventListener('error', () => reject(new Error('Upload failed')));
    xhr.addEventListener('abort', () => reject(new Error('Upload cancelled')));
    xhr.send(formData);
  });
}

function UploadProgressBar({ pct, label, compact }) {
  const safePct = Number.isFinite(pct) ? Math.max(0, Math.min(100, pct)) : 0;
  const indeterminate = safePct === 0;
  return (
    <div className={'upload-progress' + (compact ? ' compact' : '') + (indeterminate ? ' indeterminate' : '')}>
      <div className="upload-progress-label">{label || `Uploading ${safePct}%`}</div>
      <div className="progress-bar">
        <div className="progress-fill" style={{ width: `${indeterminate ? 35 : safePct}%` }} />
      </div>
    </div>
  );
}

function permissionScreen(screen) {
  return screen === 'files' ? 'pipeline' : screen;
}

function can(screen, action) {
  if (!authUser) return false;
  if (authUser.role === 'admin') return true;
  const scope = permissionScreen(screen);
  const p = authUser.permissions || {};
  if (!p.screens?.[scope]) return false;
  return !!p.actions?.[scope]?.[action];
}

function canView(screen) {
  if (!authUser) return false;
  if (authUser.role === 'admin') return true;
  return !!authUser.permissions?.screens?.[permissionScreen(screen)];
}

function exportExcel(path, filename) {
  fetch(API + path, { credentials: 'include' }).then(async res => {
    if (!res.ok) throw new Error('Export failed');
    const blob = await res.blob();
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = filename;
    a.click();
  }).catch(e => toast(e.message));
}

// ─── Constants ──────────────────────────────────────────────
const STAGES = [
  { id: 'idea', label: 'Idea' },
  { id: 'cutting', label: 'Cutting' },
  { id: 'qc', label: 'QC' },
  { id: 'thumbnail', label: 'Thumbnail' },
  { id: 'ready', label: 'Ready' },
  { id: 'published', label: 'Published' },
];

function stageLabel(stageId) {
  return STAGES.find(s => s.id === stageId)?.label || stageId;
}

function stageColor(stageId) {
  switch (stageId) {
    case 'idea': return '#7c6cff';
    case 'cutting': return '#4A9EFF';
    case 'thumbnail': return '#f39c12';
    case 'qc': return '#9b59b6';
    case 'ready': return '#2ecc71';
    case 'published': return '#e63946';
    default: return '#8888A8';
  }
}

// ─── Toast ──────────────────────────────────────────────────
let toastFn = null;
function Toast() {
  const [msg, setMsg] = useState(null);
  useEffect(() => { toastFn = (m) => { setMsg(m); setTimeout(() => setMsg(null), 2600); }; }, []);
  if (!msg) return null;
  return <div className="toast">{msg}</div>;
}
function toast(m) { if (toastFn) toastFn(m); }

// ─── Small UI helpers ───────────────────────────────────────
function Spinner() { return <div className="spin" />; }

function Badge({ children, color }) {
  return <span className="badge" style={{ background: color + '22', color }}>{children}</span>;
}

function categoryColor(cats, name) {
  const c = cats.find(x => x.name === name);
  return c ? c.color : '#8888A8';
}

function daysInStage(enteredAt) {
  if (!enteredAt) return 0;
  const ms = Date.now() - new Date(enteredAt.replace(' ', 'T') + 'Z').getTime();
  return Math.floor(ms / 86400000);
}

function timeInStageLabel(enteredAt, stageName) {
  const d = daysInStage(enteredAt);
  const label = stageLabel(stageName);
  if (d === 0) return 'Today in ' + label;
  if (d === 1) return '1 day in ' + label;
  return d + ' days in ' + label;
}

function stuckClass(enteredAt, warningDays) {
  const d = daysInStage(enteredAt);
  if (d >= (warningDays || 3) + 2) return 'stuck-critical';
  if (d >= (warningDays || 3)) return 'stuck-warn';
  return '';
}

function getInitials(name) {
  return String(name || '')
    .split(' ')
    .map(word => word[0])
    .join('')
    .slice(0, 2)
    .toUpperCase() || 'U';
}

function avatarUrl(userId, avatarUpdatedAt) {
  if (!userId) return null;
  return `${API}/users/${userId}/avatar?v=${encodeURIComponent(avatarUpdatedAt || '')}`;
}

function Avatar({ userId, name, color, avatarPath, avatarUpdatedAt, size = 28, className = '' }) {
  const initials = getInitials(name);
  const style = { width: size, height: size, minWidth: size, minHeight: size };
  if (avatarPath && userId) {
    return <img className={'avatar-image ' + className} style={style} src={avatarUrl(userId, avatarUpdatedAt)} alt={name || 'User'} />;
  }
  return <span className={'avatar-fallback ' + className} style={{ ...style, background: color || '#E63946' }}>{initials}</span>;
}

function AssigneeAvatar({ userId, name, color, avatarPath, avatarUpdatedAt, meta, compact }) {
  if (!name) return <span className="assignee-none">Unassigned</span>;
  return (
    <span className={'assignee' + (compact ? ' compact' : '')}>
      <Avatar
        userId={userId}
        name={name}
        color={color}
        avatarPath={avatarPath}
        avatarUpdatedAt={avatarUpdatedAt}
        size={compact ? 22 : 26}
      />
      <span className="assignee-copy">
        <span className="assignee-name">{name}</span>
        {meta ? <span className="assignee-meta">{meta}</span> : null}
      </span>
    </span>
  );
}

function UserPicker({ users, value, onChange, placeholder = 'Select assignee', allowNone = true, disabled = false }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const selected = (users || []).find(user => String(user.id) === String(value || '')) || null;

  useEffect(() => {
    function onPointerDown(event) {
      if (!ref.current?.contains(event.target)) setOpen(false);
    }
    document.addEventListener('mousedown', onPointerDown);
    return () => document.removeEventListener('mousedown', onPointerDown);
  }, []);

  return (
    <div className={'user-picker' + (disabled ? ' disabled' : '')} ref={ref}>
      <button
        type="button"
        className="user-picker-trigger"
        disabled={disabled}
        onClick={() => setOpen(prev => !prev)}
      >
        {selected ? (
          <AssigneeAvatar
            userId={selected.id}
            name={selected.name}
            color={selected.avatar_color}
            avatarPath={selected.avatar_path}
            avatarUpdatedAt={selected.avatar_updated_at}
            meta={selected.role}
          />
        ) : (
          <span className="user-picker-placeholder">{placeholder}</span>
        )}
        <span className={'user-picker-caret' + (open ? ' open' : '')}>+</span>
      </button>
      {open && (
        <div className="user-picker-menu">
          {allowNone && (
            <button type="button" className="user-picker-option" onClick={() => { onChange(null); setOpen(false); }}>
              <span className="assignee-none">Unassigned</span>
            </button>
          )}
          {(users || []).map(user => (
            <button
              key={user.id}
              type="button"
              className={'user-picker-option' + (selected?.id === user.id ? ' active' : '')}
              onClick={() => { onChange(user.id); setOpen(false); }}
            >
              <AssigneeAvatar
                userId={user.id}
                name={user.name}
                color={user.avatar_color}
                avatarPath={user.avatar_path}
                avatarUpdatedAt={user.avatar_updated_at}
                meta={user.role}
              />
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function MentionPicker({ users, onPick }) {
  return (
    <div className="mention-picker">
      {(users || []).map(user => (
        <button
          key={user.id}
          type="button"
          className="mention-chip"
          onClick={() => onPick(`@[${user.name}]`)}
        >
          <Avatar
            userId={user.id}
            name={user.name}
            color={user.avatar_color}
            avatarPath={user.avatar_path}
            avatarUpdatedAt={user.avatar_updated_at}
            size={18}
          />
          <span>@{user.name}</span>
        </button>
      ))}
    </div>
  );
}

function renderCommentBody(body) {
  const text = String(body || '');
  const parts = text.split(/(@\[[^\]]+\])/g).filter(Boolean);
  return parts.map((part, index) => {
    const match = part.match(/^@\[(.+)\]$/);
    if (match) {
      return <span key={index} className="comment-mention">@{match[1]}</span>;
    }
    return <React.Fragment key={index}>{part}</React.Fragment>;
  });
}

function SeriesCover({ series, onUploaded, onCoverChange, compact, inline }) {
  const fileRef = useRef();
  const [uploading, setUploading] = useState(false);
  const [uploadPct, setUploadPct] = useState(0);
  const [cacheBust, setCacheBust] = useState(series.updated_at || series.cover_path || '');

  useEffect(() => {
    setCacheBust(series.updated_at || series.cover_path || '');
  }, [series.updated_at, series.cover_path]);

  const hasCover = !!series.cover_path;
  const imgUrl = hasCover
    ? `${API}/series/${series.id}/cover?v=${encodeURIComponent(cacheBust)}`
    : null;

  function stop(e) { e.stopPropagation(); }

  async function upload(file) {
    if (!file) return;
    setUploading(true);
    setUploadPct(0);
    try {
      const fd = new FormData();
      fd.append('cover', file);
      const data = await uploadFormData(`/series/${series.id}/cover`, fd, {
        onProgress: setUploadPct,
      });
      setCacheBust(data.updated_at || String(Date.now()));
      if (onCoverChange) onCoverChange(data);
      if (onUploaded) onUploaded(data);
      toast('Cover uploaded');
    } catch (e) { toast(e.message); }
    finally {
      setUploadPct(0);
      setUploading(false);
    }
  }

  async function removeCover(e) {
    stop(e);
    if (!confirm('Remove cover?')) return;
    try {
      const data = await api(`/series/${series.id}/cover`, { method: 'DELETE' });
      setCacheBust('');
      if (onCoverChange) onCoverChange(data);
      if (onUploaded) onUploaded(data);
      toast('Cover removed');
    } catch (e) { toast(e.message); }
  }

  function downloadCover(e) {
    stop(e);
    triggerDownload(`${API}/series/${series.id}/cover/download`, series.cover_path || 'cover.jpg');
  }

  if (inline) {
    if (!can('pipeline', 'edit')) return null;
    return (
      <>
        <button type="button" className="series-cover-upload-inline" title="Upload cover"
          disabled={uploading} onClick={e => { stop(e); fileRef.current?.click(); }}>
          {uploading ? <Spinner /> : <NavIcon name="Upload" size={14} />}
        </button>
        {uploading && <UploadProgressBar pct={uploadPct} compact />}
        <input ref={fileRef} type="file" accept="image/jpeg,image/png,image/webp" style={{ display: 'none' }}
          onChange={e => { upload(e.target.files[0]); e.target.value = ''; }} />
      </>
    );
  }

  if (compact && !hasCover) return null;

  return (
    <div className={'series-cover' + (compact ? ' series-cover-compact' : '')}>
      {hasCover ? (
        <img src={imgUrl} alt="" />
      ) : (
        <div className="series-cover-placeholder">
          <NavIcon name="Image" size={32} />
          <span>Add cover</span>
        </div>
      )}
      {uploading && (
        <div className="upload-progress-overlay">
          <UploadProgressBar pct={uploadPct} label={`Uploading ${uploadPct || 0}%`} />
        </div>
      )}
      <div className="series-cover-actions">
        {can('pipeline', 'edit') && (
          <>
            <button type="button" className="series-cover-btn" title={hasCover ? 'Replace cover' : 'Upload cover'}
              disabled={uploading} onClick={e => { stop(e); fileRef.current?.click(); }}>
              {uploading ? <Spinner /> : <NavIcon name="Upload" size={compact ? 12 : 16} />}
            </button>
            <input ref={fileRef} type="file" accept="image/jpeg,image/png,image/webp" style={{ display: 'none' }}
              onChange={e => { upload(e.target.files[0]); e.target.value = ''; }} />
          </>
        )}
        {hasCover && canView('pipeline') && (
          <button type="button" className="series-cover-btn" title="Download cover" onClick={downloadCover}>
            <NavIcon name="Download" size={compact ? 12 : 16} />
          </button>
        )}
        {hasCover && can('pipeline', 'edit') && !compact && (
          <button type="button" className="series-cover-btn" title="Remove cover" onClick={removeCover}>
            <NavIcon name="X" size={16} />
          </button>
        )}
      </div>
    </div>
  );
}

function formatBytes(bytes) {
  const size = Number(bytes) || 0;
  if (!size) return '0 B';
  const units = ['B', 'KB', 'MB', 'GB'];
  let value = size;
  let unit = units[0];
  for (let i = 0; i < units.length; i += 1) {
    unit = units[i];
    if (value < 1024 || i === units.length - 1) break;
    value /= 1024;
  }
  return `${value >= 10 ? Math.round(value) : value.toFixed(1)} ${unit}`;
}

function formatDateTime(value) {
  if (!value) return '';
  const d = new Date(String(value).replace(' ', 'T') + 'Z');
  if (Number.isNaN(d.getTime())) return value;
  return d.toLocaleString();
}

function triggerDownload(url, fallbackName = 'download') {
  const a = document.createElement('a');
  a.href = url;
  a.download = fallbackName;
  a.rel = 'noopener';
  a.style.display = 'none';
  document.body.appendChild(a);
  a.click();
  setTimeout(() => a.remove(), 2000);
}

function SeriesPartsActions({ series, onChanged, compact }) {
  const fileRef = useRef();
  const [uploading, setUploading] = useState(false);
  const [uploadPct, setUploadPct] = useState(0);
  const partCount = Number(series.episode_count || 0);

  function stop(e) { e.stopPropagation(); }

  async function upload(files) {
    const list = Array.from(files || []).filter(Boolean);
    if (!list.length) return;
    setUploading(true);
    setUploadPct(0);
    try {
      const fd = new FormData();
      list.forEach(file => fd.append('parts', file));
      const data = await uploadFormData(`/series/${series.id}/parts`, fd, {
        onProgress: setUploadPct,
      });
      onChanged && onChanged(data.series);
      toast(`Uploaded ${list.length} part${list.length !== 1 ? 's' : ''}`);
    } catch (e) {
      toast(e.message);
    } finally {
      setUploadPct(0);
      setUploading(false);
    }
  }

  if (!can('pipeline', 'edit') && !(canView('pipeline') && partCount > 0)) return null;

  return (
    <div className={'series-parts-actions' + (compact ? ' compact' : '')} onClick={stop}>
      {can('pipeline', 'edit') && (
        <>
          <button type="button" className="series-cover-upload-inline" title="Upload parts"
            disabled={uploading} onClick={e => { stop(e); fileRef.current?.click(); }}>
            {uploading ? <Spinner /> : <NavIcon name="Upload" size={14} />}
          </button>
          <input
            ref={fileRef}
            type="file"
            accept="video/*"
            multiple
            style={{ display: 'none' }}
            onChange={e => { upload(e.target.files); e.target.value = ''; }}
          />
        </>
      )}
      {uploading && <UploadProgressBar pct={uploadPct} compact label={`Uploading ${uploadPct || 0}%`} />}
      {canView('pipeline') && (partCount > 0 || series.cover_path) && (
        <button type="button" className="series-cover-upload-inline" title="Download all files"
          onClick={e => { stop(e); triggerDownload(`${API}/series/${series.id}/files/download`, `${series.storage_folder_name || series.title || 'series'}.zip`); }}>
          <NavIcon name="Download" size={14} />
        </button>
      )}
    </div>
  );
}

function SeriesPartsManager({ series, onChanged }) {
  const [parts, setParts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [uploading, setUploading] = useState(false);
  const [uploadPct, setUploadPct] = useState(0);
  const fileRef = useRef();

  const loadParts = useCallback(async () => {
    setLoading(true);
    try {
      setParts(await api(`/series/${series.id}/parts`));
    } catch (e) {
      toast(e.message);
    }
    setLoading(false);
  }, [series.id]);

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

  async function upload(files) {
    const list = Array.from(files || []).filter(Boolean);
    if (!list.length) return;
    setUploading(true);
    setUploadPct(0);
    try {
      const fd = new FormData();
      list.forEach(file => fd.append('parts', file));
      const data = await uploadFormData(`/series/${series.id}/parts`, fd, {
        onProgress: setUploadPct,
      });
      setParts(data.parts || []);
      onChanged && onChanged(data.series);
      toast(`Uploaded ${list.length} part${list.length !== 1 ? 's' : ''}`);
    } catch (e) {
      toast(e.message);
    } finally {
      setUploadPct(0);
      setUploading(false);
    }
  }

  async function removePart(partId) {
    if (!confirm('Delete this part?')) return;
    try {
      const data = await api(`/series/${series.id}/parts/${partId}`, { method: 'DELETE' });
      setParts(data.parts || []);
      onChanged && onChanged(data.series);
      toast('Part deleted');
    } catch (e) {
      toast(e.message);
    }
  }

  return (
    <div className="field">
      <label>Series Parts</label>
      <div className="series-folder-meta">
        <div><b>Folder</b>: {series.storage_folder_name || series.title}</div>
        {series.stage === 'published' && (
          <div className="series-cleanup-note">
            Local media auto-deletes 24 hours after publish
            {series.media_cleanup_due_at ? ` (${formatDateTime(series.media_cleanup_due_at)})` : ''}
          </div>
        )}
      </div>

      <div className="series-parts-toolbar">
        {can('pipeline', 'edit') && (
          <>
            <button type="button" className="btn btn-sm" onClick={() => fileRef.current?.click()} disabled={uploading}>
              <NavIcon name="Upload" size={14} /> {uploading ? `Uploading ${uploadPct || 0}%` : 'Upload Parts'}
            </button>
            <input
              ref={fileRef}
              type="file"
              accept="video/*"
              multiple
              style={{ display: 'none' }}
              onChange={e => { upload(e.target.files); e.target.value = ''; }}
            />
          </>
        )}
        {(series.cover_path || parts.some(p => p.is_available)) && (
          <button type="button" className="btn-ghost btn btn-sm" onClick={() => triggerDownload(`${API}/series/${series.id}/files/download`, `${series.storage_folder_name || series.title || 'series'}.zip`)}>
            <NavIcon name="Download" size={14} /> Download All ZIP
          </button>
        )}
        <span className="series-parts-count">{parts.length} part{parts.length !== 1 ? 's' : ''}</span>
      </div>
      {uploading && <UploadProgressBar pct={uploadPct} label={`Uploading ${uploadPct || 0}%`} />}

      {loading ? <Spinner /> : (
        parts.length ? (
          <div className="series-parts-list">
            {parts.map(part => (
              <div key={part.id} className={'series-part-row' + (!part.is_available ? ' unavailable' : '')}>
                <div className="series-part-main">
                  <div className="series-part-title">Part {part.part_number || part.episode_number || '?'}</div>
                  <div className="series-part-meta">
                    {part.file_name || 'No file'}
                    {part.file_size ? ` • ${formatBytes(part.file_size)}` : ''}
                    {part.duration ? ` • ${Math.round(part.duration)}s` : ''}
                    {!part.is_available ? ' • local file removed' : ''}
                  </div>
                </div>
                <div className="series-part-actions-row">
                  {part.is_available && (
                    <button type="button" className="btn-ghost btn btn-sm" onClick={() => triggerDownload(`${API}/series/${series.id}/parts/${part.id}/download`, part.file_name || `part-${part.part_number || part.episode_number || 'file'}.mp4`)}>
                      <NavIcon name="Download" size={14} /> Download
                    </button>
                  )}
                  {can('pipeline', 'edit') && (
                    <button type="button" className="btn-ghost btn btn-sm" onClick={() => removePart(part.id)}>
                      <NavIcon name="X" size={14} /> Delete
                    </button>
                  )}
                </div>
              </div>
            ))}
          </div>
        ) : (
          <p className="series-parts-empty">No parts uploaded yet. Upload files named `1`, `2`, `3` and so on.</p>
        )
      )}
    </div>
  );
}

function QCPartsPlayer({ seriesId }) {
  const [parts, setParts] = useState([]);
  const [activeId, setActiveId] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let mounted = true;
    (async () => {
      setLoading(true);
      try {
        const rows = await api(`/series/${seriesId}/parts`);
        if (!mounted) return;
        setParts(rows);
        setActiveId(rows[0]?.id || null);
      } catch (e) {
        if (mounted) toast(e.message);
      }
      if (mounted) setLoading(false);
    })();
    return () => { mounted = false; };
  }, [seriesId]);

  if (loading) return <Spinner />;
  if (!parts.length) return <div className="qc-player-empty">No parts uploaded yet.</div>;

  const activeIndex = Math.max(parts.findIndex(p => p.id === activeId), 0);
  const active = parts[activeIndex];
  if (!active) return <div className="qc-player-empty">No playable parts found.</div>;

  return (
    <div className="qc-player-card">
      <video key={active.id} className="qc-video" controls preload="metadata" src={`${API}/series/${seriesId}/parts/${active.id}/stream`} />
      <div className="qc-player-toolbar">
        <button type="button" className="btn-ghost btn btn-sm" disabled={activeIndex === 0} onClick={() => setActiveId(parts[activeIndex - 1].id)}>
          <NavIcon name="ChevronLeft" size={14} /> Prev
        </button>
        <div className="qc-player-title">Part {active.part_number || active.episode_number || activeIndex + 1}</div>
        <button type="button" className="btn-ghost btn btn-sm" disabled={activeIndex === parts.length - 1} onClick={() => setActiveId(parts[activeIndex + 1].id)}>
          Next
        </button>
      </div>
      <div className="qc-part-switcher">
        {parts.map(part => (
          <button
            key={part.id}
            type="button"
            className={'qc-part-chip' + (part.id === active.id ? ' active' : '')}
            onClick={() => setActiveId(part.id)}
          >
            Part {part.part_number || part.episode_number}
          </button>
        ))}
      </div>
    </div>
  );
}

const I = () => window.REDZ_ICONS || {};

function NavIcon({ name, size = 20 }) {
  const Icon = I()[name];
  return Icon ? <span className="nav-ico"><Icon size={size} /></span> : null;
}

function CloseButton({ onClick, label = 'Close' }) {
  return (
    <button type="button" className="close-btn" onClick={onClick} aria-label={label}>
      <NavIcon name="X" size={18} />
    </button>
  );
}

function PageHeader({ eyebrow, title, subtitle, meta, actions }) {
  return (
    <div className="page-header">
      <div className="page-header-row">
        <div className="page-copy">
          {eyebrow && <div className="page-eyebrow">{eyebrow}</div>}
          {title && <h2 className="page-title">{title}</h2>}
          {subtitle && <p className="page-subtitle">{subtitle}</p>}
          {meta && <div className="page-meta">{meta}</div>}
        </div>
        {actions && <div className="page-actions">{actions}</div>}
      </div>
    </div>
  );
}

function ContextPill({ label, value, tone }) {
  return (
    <span className={'context-pill' + (tone ? ' ' + tone : '')}>
      {label ? <span className="context-pill-label">{label}</span> : null}
      <b>{value}</b>
    </span>
  );
}

function StatCard({ label, value, sub, accent, icon }) {
  return (
    <div className="stat-card">
      <div className="stat-card-header">
        <div className="stat-label">{label}</div>
        {icon && <span className="section-title-icon">{icon}</span>}
      </div>
      <div className="stat-value" style={accent ? { color: accent } : undefined}>{value}</div>
      {sub && <div className="stat-sub">{sub}</div>}
    </div>
  );
}

function EmptyState({ icon, title, message, action }) {
  return (
    <div className="empty">
      {icon && <div className="empty-icon">{icon}</div>}
      {title && <h3>{title}</h3>}
      {message && <p>{message}</p>}
      {action}
    </div>
  );
}

function FilterBar({ children }) {
  return <div className="filter-bar">{children}</div>;
}

function AlertBanner({ children }) {
  return (
    <div className="alert-banner" role="alert">
      <span className="alert-banner-icon"><NavIcon name="AlertTriangle" size={18} /></span>
      <span>{children}</span>
    </div>
  );
}

function SectionTitle({ children, icon, style }) {
  return (
    <div className="section-title" style={style}>
      {icon && <span className="section-title-icon">{icon}</span>}
      {children}
    </div>
  );
}

const NAV_CONFIG = {
  dashboard: { label: 'Dashboard', icon: 'LayoutDashboard', group: 'ops', mobileTab: true },
  pipeline: { label: 'Production Pipeline', icon: 'Kanban', group: 'ops', mobileTab: true },
  files: { label: 'Files & Media', icon: 'FileText', group: 'ops' },
  accounts: { label: 'Accounts', icon: 'Smartphone', group: 'content' },
  research: { label: 'Ideas', icon: 'Search', group: 'content', mobileTab: true },
  aistudio: { label: 'AI Studio', icon: 'PenLine', group: 'content' },
  qc: { label: 'Quality Control', icon: 'CheckCircle', group: 'ops', mobileTab: true },
  performance: { label: 'Performance', icon: 'TrendingUp', group: 'content' },
  reports: { label: 'Reports', icon: 'FileText', group: 'content' },
  video: { label: 'Video Splitter', icon: 'Scissors', group: 'content' },
  users: { label: 'Users', icon: 'Users', group: 'admin' },
  settings: { label: 'Settings', icon: 'Settings', group: 'admin' },
};

const NAV_GROUPS = {
  ops: 'Operations',
  content: 'Content',
  admin: 'Administration',
};

const SCREEN_META = {
  dashboard: {
    title: 'Operations Dashboard',
    subtitle: 'Daily command center for production flow, bottlenecks, and performance signals.',
  },
  pipeline: {
    title: 'Production Pipeline',
    subtitle: 'Move series from raw idea to published output with clearer stage ownership.',
  },
  files: {
    title: 'Files & Media',
    subtitle: 'Browse every series folder, preview covers and parts, and download files without opening each card separately.',
  },
  accounts: {
    title: 'Accounts',
    subtitle: 'Manage publishing destinations, ownership, and production capacity across accounts.',
  },
  research: {
    title: 'Ideas',
    subtitle: 'Create single ideas or bulk idea rows directly in the pipeline with automatic editor assignment.',
  },
  aistudio: {
    title: 'AI Studio',
    subtitle: 'Generate scripts, thumbnail prompts, and captions with a more focused writing workspace.',
  },
  qc: {
    title: 'Quality Control',
    subtitle: 'Review portrait-first series parts, capture feedback, and approve confidently.',
  },
  performance: {
    title: 'Performance',
    subtitle: 'Track winners, underperformers, and which categories deserve more production.',
  },
  reports: {
    title: 'Reports',
    subtitle: 'See production output by time range, account, category, and stage health.',
  },
  video: {
    title: 'Video Splitter',
    subtitle: 'Prepare source material for production with smarter upload, split, and export flows.',
  },
  users: {
    title: 'Users',
    subtitle: 'Manage team access, roles, and permissions for every operating area.',
  },
  settings: {
    title: 'Settings',
    subtitle: 'Tune targets, AI integrations, categories, and workflow thresholds in one place.',
  },
};

const MOBILE_TABS = ['dashboard', 'pipeline', 'research', 'qc'];

// ════════════════════════════════════════════════════════════
// LOGIN
// ════════════════════════════════════════════════════════════
function LoginScreen({ onLogin }) {
  const [email, setEmail] = useState('admin@redz.local');
  const [password, setPassword] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  async function submit(e) {
    e.preventDefault();
    setLoading(true); setError('');
    try {
      const res = await fetch(API + '/auth/login', {
        method: 'POST', credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || 'Login failed');
      authUser = data.user;
      onLogin(data.user);
    } catch (err) { setError(err.message); }
    setLoading(false);
  }

  return (
    <div className="login-page">
      <div className="login-card">
        <div className="logo" style={{ border: 'none', padding: '0 0 24px' }}>
          <div className="logo-mark">R</div>
          <div className="logo-text">REDZ<span> ENGINE</span></div>
        </div>
        <h2>Sign in</h2>
        <p className="login-sub">Internal content-production command center</p>
        <form onSubmit={submit}>
          <div className="field"><label>Email</label><input type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" /></div>
          <div className="field">
            <label>Password</label>
            <div className="password-field">
              <input type={showPw ? 'text' : 'password'} value={password} onChange={e => setPassword(e.target.value)} required autoComplete="current-password" style={{ paddingLeft: 44 }} />
              <button type="button" className="password-toggle" onClick={() => setShowPw(!showPw)} aria-label={showPw ? 'Hide password' : 'Show password'}>
                <NavIcon name={showPw ? 'EyeOff' : 'Eye'} size={18} />
              </button>
            </div>
          </div>
          {error && (
            <div className="login-error">
              <NavIcon name="AlertTriangle" size={16} />
              <span>{error}</span>
            </div>
          )}
          <button className="btn login-btn" type="submit" disabled={loading}>{loading ? 'Signing in…' : 'Sign In'}</button>
        </form>
      </div>
    </div>
  );
}

function ProfileSetupGate({ user, onUploaded, onLogout }) {
  const [file, setFile] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [uploadPct, setUploadPct] = useState(0);
  const [error, setError] = useState('');

  async function submit(e) {
    e.preventDefault();
    if (!file) return setError('Profile picture required');
    setUploading(true);
    setUploadPct(0);
    setError('');
    try {
      const fd = new FormData();
      fd.append('avatar', file);
      const data = await uploadFormData('/users/me/avatar', fd, {
        onProgress: setUploadPct,
      });
      authUser = data.user;
      onUploaded(data.user);
    } catch (err) {
      setError(err.message);
      setUploading(false);
      setUploadPct(0);
    }
  }

  return (
    <div className="login-page">
      <div className="login-card profile-setup-card">
        <div className="logo" style={{ border: 'none', padding: '0 0 20px' }}>
          <div className="logo-mark">R</div>
          <div className="logo-text">REDZ<span> ENGINE</span></div>
        </div>
        <h2>Upload your profile picture</h2>
        <p className="login-sub">This is required on first login. Your picture will appear on pipeline cards, assignment pickers, and comments.</p>
        <form onSubmit={submit}>
          <div className="profile-setup-preview">
            <Avatar
              userId={file ? null : user.id}
              name={user.name}
              color={user.avatar_color}
              avatarPath={file ? null : user.avatar_path}
              avatarUpdatedAt={user.avatar_updated_at}
              size={72}
            />
            <div>
              <div className="user-name">{user.name}</div>
              <div className="user-role">{user.role}</div>
            </div>
          </div>
          <div className="field">
            <label>Profile picture</label>
            <input type="file" accept="image/jpeg,image/png,image/webp" onChange={e => setFile(e.target.files[0] || null)} required />
          </div>
          {file ? <div className="profile-setup-file">{file.name}</div> : null}
          {uploading && <UploadProgressBar pct={uploadPct} label={`Uploading ${uploadPct || 0}%`} />}
          {error ? (
            <div className="login-error">
              <NavIcon name="AlertTriangle" size={16} />
              <span>{error}</span>
            </div>
          ) : null}
          <button className="btn login-btn" type="submit" disabled={uploading}>{uploading ? `Uploading ${uploadPct || 0}%` : 'Save and continue'}</button>
          <button type="button" className="btn-ghost btn login-btn" style={{ marginTop: 10 }} onClick={onLogout}>Sign out</button>
        </form>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// APP SHELL
// ════════════════════════════════════════════════════════════
function App() {
  const [user, setUser] = useState(null);
  const [authLoading, setAuthLoading] = useState(true);
  const [screen, setScreen] = useState('dashboard');
  const [settings, setSettings] = useState({});
  const [categories, setCategories] = useState([]);
  const [dailyCount, setDailyCount] = useState(0);
  const [moreOpen, setMoreOpen] = useState(false);
  const [drawerOpen, setDrawerOpen] = useState(false);

  useEffect(() => {
    onAuthFail = () => { authUser = null; setUser(null); };
    fetch(API + '/auth/me', { credentials: 'include' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d?.user) { authUser = d.user; setUser(d.user); } })
      .finally(() => setAuthLoading(false));
  }, []);

  const loadMeta = useCallback(async () => {
    if (!authUser) return;
    try {
      const s = await api('/settings');
      setSettings(s);
      try { setCategories(JSON.parse(s.categories || '[]')); } catch { setCategories([]); }
      if (canView('dashboard')) {
        const dash = await api('/dashboard');
        setDailyCount(dash.seriesToday);
      }
    } catch (e) { console.error(e); }
  }, []);

  useEffect(() => { if (user) loadMeta(); }, [user, loadMeta]);

  useEffect(() => {
    if (!user) return;
    const visible = Object.keys(NAV_CONFIG).filter(id => canView(id));
    if (visible.length && !visible.includes(screen)) {
      setScreen(visible.includes('pipeline') ? 'pipeline' : visible[0]);
    }
  }, [user, screen]);

  async function logout() {
    await fetch(API + '/auth/logout', { method: 'POST', credentials: 'include' });
    authUser = null; setUser(null);
  }

  function goTo(id) {
    setScreen(id);
    setMoreOpen(false);
    setDrawerOpen(false);
  }

  if (authLoading) return <div className="login-page"><Spinner /></div>;
  if (!user) return <LoginScreen onLogin={u => { authUser = u; setUser(u); }} />;
  if (!user.avatar_path) return <ProfileSetupGate user={user} onUploaded={u => setUser(u)} onLogout={logout} />;

  const allNav = Object.keys(NAV_CONFIG)
    .map(id => ({ id, ...NAV_CONFIG[id] }))
    .filter(n => canView(n.id));

  const mobileMoreItems = allNav.filter(n => !MOBILE_TABS.includes(n.id));
  const mobileTabs = [
    ...MOBILE_TABS.filter(id => canView(id)).map(id => ({ id, ...NAV_CONFIG[id] })),
    { id: '__more__', label: 'More', icon: 'MoreHorizontal' },
  ];

  const titles = Object.fromEntries(Object.entries(NAV_CONFIG).map(([k, v]) => [k, v.label]));
  const screenProps = { settings, categories, reloadMeta: loadMeta, user };
  const screenMeta = SCREEN_META[screen] || {};

  function renderNavButton(n, onClick) {
    const active = screen === n.id;
    return (
      <button key={n.id} type="button"
        className={'nav-item' + (active ? ' active' : '')}
        onClick={() => (onClick || goTo)(n.id)}>
        <span className="nav-item-copy">
          <NavIcon name={n.icon} />
          <span className="nav-item-label">{n.label}</span>
        </span>
      </button>
    );
  }

  function renderSidebarNav() {
    const groups = ['ops', 'content', 'admin'];
    return groups.map(g => {
      const items = allNav.filter(n => n.group === g);
      if (!items.length) return null;
      return (
        <div key={g}>
          <div className="nav-group-label">{NAV_GROUPS[g]}</div>
          {items.map(n => renderNavButton(n))}
        </div>
      );
    });
  }

  return (
    <div className="app">
      <aside className="sidebar">
        <div className="logo">
          <div className="logo-mark">R</div>
          <div className="logo-text">REDZ<span> ENGINE</span></div>
        </div>
        <nav className="nav">{renderSidebarNav()}</nav>
        <div className="sidebar-foot">
          <div className="user-chip">
            <Avatar
              userId={user.id}
              name={user.name}
              color={user.avatar_color}
              avatarPath={user.avatar_path}
              avatarUpdatedAt={user.avatar_updated_at}
              size={36}
            />
            <div className="user-meta">
              <div className="user-name">{user.name}</div>
              <div className="user-role">{user.role}</div>
            </div>
          </div>
          <button type="button" className="logout-btn" onClick={logout}>
            <NavIcon name="LogOut" size={14} /> Sign out
          </button>
        </div>
      </aside>

      <main className="main">
        <header className="mobile-topbar">
          <button type="button" className="icon-btn" onClick={() => setDrawerOpen(true)} aria-label="Open menu">
            <NavIcon name="Menu" size={22} />
          </button>
          <div className="mobile-topbar-copy">
            <div className="mobile-topbar-title">{titles[screen]}</div>
          </div>
          {canView('dashboard') && (
            <div className="daily-pill daily-pill-compact">
              <b>{dailyCount}</b>/{settings.daily_target || 35}
            </div>
          )}
        </header>

        <header className="topbar">
          <div className="topbar-copy">
            <h1>{screenMeta.title || titles[screen]}</h1>
            {screenMeta.subtitle && <p className="topbar-subtitle">{screenMeta.subtitle}</p>}
          </div>
          <div className="topbar-right">
            {canView('dashboard') && (
              <div className="daily-pill">Today: <b>{dailyCount}</b> / {settings.daily_target || 35} Series</div>
            )}
          </div>
        </header>

        <div className="content">
          {screen === 'dashboard' && canView('dashboard') && <Dashboard {...screenProps} go={goTo} onChange={loadMeta} />}
          {screen === 'pipeline' && canView('pipeline') && <Pipeline {...screenProps} onChange={loadMeta} />}
          {screen === 'files' && canView('files') && <FilesBrowser {...screenProps} />}
          {screen === 'accounts' && canView('accounts') && <Accounts {...screenProps} />}
          {screen === 'research' && canView('research') && <Research {...screenProps} onChange={loadMeta} />}
          {screen === 'aistudio' && canView('aistudio') && <AIStudio {...screenProps} />}
          {screen === 'qc' && canView('qc') && <QualityControl {...screenProps} onChange={loadMeta} />}
          {screen === 'performance' && canView('performance') && <Performance {...screenProps} />}
          {screen === 'reports' && canView('reports') && <Reports {...screenProps} />}
          {screen === 'video' && canView('video') && <VideoSplitter {...screenProps} />}
          {screen === 'users' && canView('users') && <UsersScreen />}
          {screen === 'settings' && canView('settings') && <Settings {...screenProps} onSaved={loadMeta} />}
        </div>
      </main>

      <nav className="bottom-nav" aria-label="Main navigation">
        {mobileTabs.map(tab => {
          if (tab.id === '__more__') {
            const moreActive = mobileMoreItems.some(n => n.id === screen);
            return (
              <button key="more" type="button"
                className={'bottom-nav-item' + (moreActive || moreOpen ? ' active' : '')}
                onClick={() => setMoreOpen(true)}
                aria-label="More screens">
                <NavIcon name="MoreHorizontal" size={22} />
                More
              </button>
            );
          }
          const active = screen === tab.id;
          return (
            <button key={tab.id} type="button"
              className={'bottom-nav-item' + (active ? ' active' : '')}
              onClick={() => goTo(tab.id)}>
              <NavIcon name={tab.icon} size={22} />
              {tab.label.split(' ')[0]}
            </button>
          );
        })}
      </nav>

      {moreOpen && (
        <>
          <div className="sheet-overlay" onClick={() => setMoreOpen(false)} />
          <div className="sheet" role="dialog" aria-label="More screens">
            <div className="sheet-handle" />
            <div className="sheet-title">More</div>
            {mobileMoreItems.map(n => (
              <button key={n.id} type="button"
                className={'sheet-item' + (screen === n.id ? ' active' : '')}
                onClick={() => goTo(n.id)}>
                <NavIcon name={n.icon} size={20} />
                {n.label}
              </button>
            ))}
          </div>
        </>
      )}

      {drawerOpen && (
        <>
          <div className="drawer-overlay" onClick={() => setDrawerOpen(false)} />
          <div className="drawer">
            <div className="logo between" style={{ borderBottom: '1px solid var(--border)' }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div className="logo-mark">R</div>
                <div className="logo-text">REDZ<span> ENGINE</span></div>
              </div>
              <button type="button" className="icon-btn" onClick={() => setDrawerOpen(false)} aria-label="Close menu">
                <NavIcon name="X" size={20} />
              </button>
            </div>
            <nav className="nav">{allNav.map(n => renderNavButton(n, goTo))}</nav>
            <div className="sidebar-foot">
              <button type="button" className="logout-btn" onClick={logout}>
                <NavIcon name="LogOut" size={14} /> Sign out
              </button>
            </div>
          </div>
        </>
      )}

      <Toast />
      <AssistantWidget />
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 1 — DASHBOARD
// ════════════════════════════════════════════════════════════
function Dashboard({ categories, settings, go }) {
  const [data, setData] = useState(null);
  const [activity, setActivity] = useState([]);

  const load = useCallback(async () => {
    try {
      setData(await api('/dashboard'));
      setActivity(await api('/activity'));
    } catch (e) { toast(e.message); }
  }, []);
  useEffect(() => { load(); }, [load]);

  if (!data) return <Spinner />;

  const target = data.dailyTarget || 35;
  const pct = Math.min(100, Math.round((data.seriesToday / target) * 100));

  return (
    <div>
      <PageHeader
        eyebrow="Today"
        title="Operations Dashboard"
        subtitle="See what needs attention first, what shipped today, and where the pipeline is slowing down."
        meta={
          <>
            <ContextPill label="Daily target" value={target} />
            <ContextPill label="Published" value={data.publishedToday} tone="success" />
            <ContextPill label="Awaiting QC" value={data.inQc} tone="warning" />
          </>
        }
        actions={
          <>
            {canView('pipeline') && <button type="button" className="btn-ghost btn btn-sm" onClick={() => go('pipeline')}>Open Pipeline</button>}
            {canView('qc') && <button type="button" className="btn-ghost btn btn-sm" onClick={() => go('qc')}>Review QC</button>}
            {canView('research') && <button type="button" className="btn btn-sm" onClick={() => go('research')}><NavIcon name="Search" size={14} /> Open Ideas</button>}
          </>
        }
      />
      {data.belowTarget && (
        <AlertBanner>
          Below daily target: <b>{data.publishedToday}</b> published vs <b>{target}</b> objective — <b>{data.deficit}</b> short
        </AlertBanner>
      )}
      <div className="grid grid-stats mb20 dashboard-stats">
        <div className="stat-card stat-card-featured">
          <div className="stat-label">Series Today</div>
          <div className="stat-value">{data.seriesToday}<span style={{ fontSize: 16, color: 'var(--text-dim)' }}> / {target}</span></div>
          <div className="progress-bar" style={{ marginTop: 10 }}>
            <div className="progress-fill" style={{ width: pct + '%' }} />
          </div>
        </div>
        <StatCard label="Episodes Today" value={data.episodesToday} sub="~3 per series" icon={<NavIcon name="Scissors" size={14} />} />
        <StatCard label="In QC" value={data.inQc} sub="awaiting review" accent="var(--warning)" icon={<NavIcon name="CheckCircle" size={14} />} />
        <StatCard label="Published Today" value={data.publishedToday} sub="live on Redz" accent="var(--success)" icon={<NavIcon name="Rocket" size={14} />} />
      </div>

      <div className="grid grid-2 mb20">
        <div className="card surface-card">
          <div className="section-title">Production by Category (Today)</div>
          <div style={{ maxHeight: 230, overflowY: 'auto' }}>
            {data.byCategory.map(c => (
              <div key={c.name} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 0' }}>
                <span style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12.5 }}>
                  <span style={{ width: 9, height: 9, borderRadius: 3, background: c.color }} />
                  {c.name}
                </span>
                <span style={{ fontSize: 12, color: 'var(--text-dim)' }}><b style={{ color: 'var(--text)' }}>{c.count}</b> / {c.daily_target}</span>
              </div>
            ))}
          </div>
        </div>
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="Clock3" size={14} />}>Recent Activity</SectionTitle>
          <div style={{ maxHeight: 230, overflowY: 'auto' }}>
            {activity.map(a => (
              <div key={a.id} style={{ display: 'flex', gap: 10, padding: '7px 0', fontSize: 12.5, alignItems: 'center' }}>
                <span style={{ width: 6, height: 6, borderRadius: 3, flexShrink: 0, background: a.type === 'success' ? 'var(--green)' : a.type === 'warning' ? 'var(--primary)' : 'var(--text-muted)' }} />
                <span style={{ flex: 1 }}>{a.message}</span>
                <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>{a.created_at?.split(' ')[1]?.slice(0, 5)}</span>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="grid grid-2 mb20">
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="AlertTriangle" size={14} />} style={{ color: 'var(--primary)' }}>Needs Attention</SectionTitle>
          {data.needsAttention.length === 0 && <p style={{ fontSize: 13, color: 'var(--text-muted)' }}>Nothing stuck. Everything flowing.</p>}
          {data.needsAttention.map(s => (
            <div key={s.id} className="list-item">
              <span className="list-item-text">{s.title}</span>
              <span className="stuck">{s.flagged == 1 ? 'flagged' : daysInStage(s.stage_entered_at) + 'd in ' + s.stage}</span>
            </div>
          ))}
        </div>
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="Trophy" size={14} />} style={{ color: 'var(--success)' }}>Today&apos;s Wins</SectionTitle>
          {data.wins.length === 0 && <p style={{ fontSize: 13, color: 'var(--text-muted)' }}>No winners logged yet. Track performance to surface them.</p>}
          {data.wins.map((w, i) => (
            <div key={i} className="list-item">
              <span className="list-item-text">{w.title}</span>
              <span style={{ color: 'var(--success)', fontWeight: 700 }}>{Number(w.ep1_views).toLocaleString()} views</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function timeInStageShort(enteredAt) {
  const d = daysInStage(enteredAt);
  if (d === 0) return 'Today';
  if (d === 1) return '1 day';
  return `${d} days`;
}

function editorAllowedStages() {
  return ['idea', 'cutting', 'qc'];
}

function CardActionMenu({ series, editorMode, onOpen, onOpenTab, onMoveStage }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const moveStages = editorMode
    ? STAGES.filter(st => editorAllowedStages().includes(st.id))
    : STAGES;

  useEffect(() => {
    function close(e) {
      if (!ref.current?.contains(e.target)) setOpen(false);
    }
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  return (
    <div className="action-menu-wrap" ref={ref} onClick={e => e.stopPropagation()}>
      <button type="button" className="action-menu-trigger" title="Actions" aria-expanded={open} onClick={() => setOpen(v => !v)}>
        <NavIcon name="MoreHorizontal" size={14} />
      </button>
      {open && (
        <div className="action-menu">
          <button type="button" className="action-menu-item" onClick={() => { setOpen(false); onOpen(); }}>Open workspace</button>
          <div className="action-menu-divider" />
          <div className="action-menu-label">Move to</div>
          {moveStages.map(st => (
            <button
              key={st.id}
              type="button"
              className="action-menu-item"
              disabled={series.stage === st.id}
              onClick={() => { setOpen(false); onMoveStage(st.id); }}
            >
              {st.label}
            </button>
          ))}
          <div className="action-menu-divider" />
          <button type="button" className="action-menu-item" onClick={() => { setOpen(false); onOpenTab('files'); }}>Upload cover / parts</button>
        </div>
      )}
    </div>
  );
}

function SeriesCard({
  series: s,
  categories,
  stuckDays,
  canMove,
  isDragging,
  editorMode,
  onDragStart,
  onDragEnd,
  onOpen,
  onOpenTab,
  onMoveStage,
}) {
  const stuck = stuckClass(s.stage_entered_at, stuckDays);
  const accountLabel = s.account_name
    ? `${s.account_name}${s.account_username ? ` · @${s.account_username}` : ''}`
    : 'No account';
  const catColor = categoryColor(categories, s.category);

  return (
    <div
      className={'series-card' + (isDragging ? ' dragging' : '')}
      draggable={canMove}
      onDragStart={onDragStart}
      onDragEnd={onDragEnd}
      onClick={onOpen}
    >
      <div className="series-card-top">
        <h4>{s.title}</h4>
        <CardActionMenu
          series={s}
          editorMode={editorMode}
          onOpen={onOpen}
          onOpenTab={onOpenTab}
          onMoveStage={onMoveStage}
        />
      </div>
      <div className="series-card-subline">{accountLabel}</div>
      {s.category && (
        <div className="series-card-meta">
          <span className="stage-dot" style={{ background: catColor }} />
          <span className="series-card-category">{s.category}</span>
        </div>
      )}
      <div className="series-card-foot">
        <AssigneeAvatar
          userId={s.assigned_user_id}
          name={s.assignee_name}
          color={s.assignee_color}
          avatarPath={s.assignee_avatar_path}
          avatarUpdatedAt={s.assignee_avatar_updated_at}
          compact
        />
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
          {!!s.episode_count && <span className="series-card-parts">{s.episode_count}p</span>}
          <span className={'stuck-time ' + stuck}>{timeInStageShort(s.stage_entered_at)}</span>
        </div>
      </div>
      {s.stage === 'published' && s.media_cleanup_due_at && !s.media_cleaned_at && (
        <div className="series-card-note">Cleanup due {formatDateTime(s.media_cleanup_due_at)}</div>
      )}
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 2 — PRODUCTION PIPELINE (Kanban)
// ════════════════════════════════════════════════════════════
function Pipeline({ categories, settings, onChange, user }) {
  const [series, setSeries] = useState([]);
  const [accounts, setAccounts] = useState([]);
  const [users, setUsers] = useState([]);
  const [filters, setFilters] = useState({ category: '', account_id: '', assigned_user_id: '' });
  const [searchQuery, setSearchQuery] = useState('');
  const [debouncedSearch, setDebouncedSearch] = useState('');
  const [viewMode, setViewMode] = useState('kanban');
  const [detail, setDetail] = useState(null);
  const [showNew, setShowNew] = useState(false);
  const [draggingId, setDraggingId] = useState(null);
  const [dropTargetStage, setDropTargetStage] = useState(null);
  const skipCardClick = useRef(false);
  const dragId = useRef(null);
  const bottleneckThreshold = parseInt(settings?.bottleneck_threshold) || 8;
  const stuckDays = parseInt(settings?.stage_stuck_warning_days) || 3;
  const editorMode = user?.role === 'editor';
  const editorColumns = [
    { id: 'todo', label: 'To Do', stages: ['idea'], empty: 'No ideas assigned to you' },
    { id: 'cutting', label: 'On Going / Cutting', stages: ['cutting'], empty: 'Nothing in progress right now' },
    { id: 'done', label: 'Done', stages: ['qc'], empty: 'No completed cuts waiting for QC' },
  ];

  useEffect(() => {
    const t = setTimeout(() => setDebouncedSearch(searchQuery.trim()), 300);
    return () => clearTimeout(t);
  }, [searchQuery]);

  const load = useCallback(async () => {
    const q = new URLSearchParams();
    Object.entries(filters).forEach(([k, v]) => { if (v) q.append(k, v); });
    if (debouncedSearch) q.append('q', debouncedSearch);
    setSeries(await api('/series?' + q.toString()));
    setAccounts(await api('/accounts'));
    try { setUsers(await api('/users/assignable')); } catch (e) { setUsers([]); }
  }, [filters, debouncedSearch]);
  useEffect(() => { load(); }, [load]);

  async function moveStageOptimistic(id, stage) {
    if (!can('pipeline', 'move_stage')) return toast('No permission to move stages');
    const item = series.find(x => x.id === id);
    if (!item || item.stage === stage) return;
    const prev = series;
    setSeries(cur => cur.map(x => x.id === id ? { ...x, stage } : x));
    try {
      const updated = await api(`/series/${id}/stage`, { method: 'PATCH', body: { stage } });
      setSeries(cur => cur.map(x => x.id === id ? { ...x, ...updated } : x));
      toast(`Moved to ${stageLabel(stage)}`);
      onChange && onChange();
    } catch (e) {
      setSeries(prev);
      toast(e.message);
    }
  }

  function handleCardDragStart(s, e) {
    if (!can('pipeline', 'move_stage')) return;
    skipCardClick.current = true;
    dragId.current = s.id;
    setDraggingId(s.id);
    e.stopPropagation();
    if (e.dataTransfer) {
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', s.id);
    }
  }

  function handleCardDragEnd() {
    dragId.current = null;
    setDraggingId(null);
    setDropTargetStage(null);
    setTimeout(() => { skipCardClick.current = false; }, 80);
  }

  function handleColumnDragEnter(stageKey) {
    setDropTargetStage(stageKey);
  }

  function handleColumnDragLeave(e, stageKey) {
    if (e.currentTarget.contains(e.relatedTarget)) return;
    setDropTargetStage(cur => (cur === stageKey ? null : cur));
  }

  function onDrop(e, stage) {
    e.preventDefault();
    e.stopPropagation();
    const id = e.dataTransfer?.getData('text/plain') || dragId.current || draggingId;
    skipCardClick.current = true;
    setDropTargetStage(null);
    setDraggingId(null);
    dragId.current = null;
    if (id) moveStageOptimistic(id, stage);
  }

  function openDetail(s, tab) {
    if (skipCardClick.current) return;
    setDetail({ series: s, tab: tab || 'overview' });
  }

  const onSeriesUpdated = useCallback((updated) => {
    setSeries(prev => prev.map(x => x.id === updated.id ? { ...x, ...updated } : x));
    setDetail(prev => prev?.series?.id === updated.id ? { ...prev, series: { ...prev.series, ...updated } } : prev);
  }, []);

  function renderCard(s) {
    return (
      <SeriesCard
        key={s.id}
        series={s}
        categories={categories}
        stuckDays={stuckDays}
        canMove={can('pipeline', 'move_stage')}
        isDragging={draggingId === s.id}
        editorMode={editorMode}
        onDragStart={e => handleCardDragStart(s, e)}
        onDragEnd={handleCardDragEnd}
        onOpen={() => openDetail(s, 'overview')}
        onOpenTab={tab => openDetail(s, tab)}
        onMoveStage={stage => moveStageOptimistic(s.id, stage)}
      />
    );
  }

  function renderKanbanColumns(columns) {
    return (
      <div className="kanban-wrap">
        <div className="kanban">
        {columns.map(stage => {
          const items = series.filter(s => (stage.stages || [stage.id]).includes(s.stage));
          const dropStage = (stage.stages || [stage.id])[0];
          const stageKey = stage.id;
          const isBottleneck = items.length > bottleneckThreshold;
          const isDropActive = dropTargetStage === stageKey;
          return (
            <div
              key={stage.id}
              data-stage={dropStage}
              className={'kanban-col' + (isDropActive ? ' drop-target-active' : '') + (isBottleneck ? ' bottleneck' : '')}
              onDragOver={e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; }}
              onDragEnter={() => handleColumnDragEnter(stageKey)}
              onDragLeave={e => handleColumnDragLeave(e, stageKey)}
              onDrop={e => onDrop(e, dropStage)}
            >
              <div className="kanban-head">
                <h3>{stage.label}{isBottleneck && <span className="bottleneck-badge"><NavIcon name="AlertTriangle" size={14} /></span>}</h3>
                <span className={'kanban-count' + (isBottleneck ? ' bottleneck-count' : '')}>{items.length}</span>
              </div>
              <div className="kanban-body" onDragOver={e => e.preventDefault()}>
                {items.map(renderCard)}
                {items.length === 0 && <div className="kanban-empty">{stage.empty || 'Drag a card here'}</div>}
              </div>
            </div>
          );
        })}
        </div>
      </div>
    );
  }

  function renderSwimlanes() {
    const laneAccounts = accounts.length ? accounts : [{ id: '_none', name: 'Unassigned' }];
    return (
      <div className="swimlanes">
        {laneAccounts.map(acct => {
          const laneSeries = series.filter(s => acct.id === '_none' ? !s.account_id : s.account_id === acct.id);
          if (!laneSeries.length && ((filters.account_id && filters.account_id !== acct.id) || debouncedSearch)) return null;
          return (
            <div key={acct.id} className="swimlane">
              <div className="swimlane-head"><b>{acct.name}</b> <span className="kanban-count">{laneSeries.length}</span></div>
              <div className="swimlane-stages">
                {STAGES.map(st => {
                  const items = laneSeries.filter(s => s.stage === st.id);
                  const isDropActive = dropTargetStage === st.id;
                  return (
                    <div
                      key={st.id}
                      data-stage={st.id}
                      className={'swimlane-col' + (isDropActive ? ' drop-target-active' : '')}
                      onDragOver={e => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; }}
                      onDragEnter={() => handleColumnDragEnter(st.id)}
                      onDragLeave={e => handleColumnDragLeave(e, st.id)}
                      onDrop={e => onDrop(e, st.id)}
                    >
                      <div className="swimlane-stage-label">{st.label} ({items.length})</div>
                      {items.map(renderCard)}
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  return (
    <div>
      <PageHeader
        eyebrow={editorMode ? 'Assigned work' : 'Workflow'}
        title={editorMode ? 'Editor Pipeline' : 'Production Pipeline'}
        subtitle={editorMode
          ? 'Drag cards between columns or use the menu to move stages. Click a card to open the workspace.'
          : 'Drag cards between stages, use the card menu for quick moves, or open a card for full details.'}
        meta={
          <>
            <ContextPill label="Visible series" value={series.length} />
            <ContextPill label="Bottleneck limit" value={bottleneckThreshold} tone="warning" />
            {debouncedSearch && <ContextPill label="Search" value={debouncedSearch} />}
          </>
        }
        actions={
          <>
            {can('pipeline', 'export') && <button type="button" className="btn-ghost btn" onClick={() => exportExcel('/export/series', 'redz-pipeline.xlsx')}>Export Excel</button>}
            {!editorMode && can('pipeline', 'create') && <button type="button" className="btn" onClick={() => setShowNew(true)}><NavIcon name="Plus" size={16} /> New Series</button>}
          </>
        }
      />
      <div className="card filter-panel surface-card mb20">
      <FilterBar>
        <div className="pipeline-search">
          <span className="pipeline-search-icon"><NavIcon name="Search" size={18} /></span>
          <input
            type="search"
            value={searchQuery}
            onChange={e => setSearchQuery(e.target.value)}
            placeholder="Smart search: qc matshoufoh, stuck رعب, unassigned…"
            aria-label="Search pipeline"
          />
          {searchQuery && (
            <button type="button" className="pipeline-search-clear" onClick={() => setSearchQuery('')} aria-label="Clear search">
              <NavIcon name="X" size={16} />
            </button>
          )}
        </div>
        {debouncedSearch && (
          <span className="search-results-pill">{series.length} result{series.length !== 1 ? 's' : ''}</span>
        )}
        <select value={filters.category} onChange={e => setFilters({ ...filters, category: e.target.value })}>
          <option value="">All Categories</option>
          {categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
        </select>
        {!editorMode && (
          <select value={filters.account_id} onChange={e => setFilters({ ...filters, account_id: e.target.value })}>
            <option value="">All Accounts</option>
            {accounts.map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
          </select>
        )}
        {!editorMode && (
          <select value={filters.assigned_user_id} onChange={e => setFilters({ ...filters, assigned_user_id: e.target.value })}>
            <option value="">All Assignees</option>
            {users.map(u => <option key={u.id} value={u.id}>{u.name}</option>)}
          </select>
        )}
        {!editorMode && (
          <div className="tabs tabs-fit" style={{ marginBottom: 0 }}>
            <button type="button" className={'tab' + (viewMode === 'kanban' ? ' active' : '')} onClick={() => setViewMode('kanban')}>Kanban</button>
            <button type="button" className={'tab' + (viewMode === 'swimlanes' ? ' active' : '')} onClick={() => setViewMode('swimlanes')}>Swimlanes</button>
          </div>
        )}
      </FilterBar>
      </div>

      {debouncedSearch && series.length === 0 && (
        <div className="card mb20">
          <EmptyState
            icon={<NavIcon name="Search" size={40} />}
            title="No series match your search"
            message={'Nothing found for "' + debouncedSearch + '". Try account username, stage (qc, idea), stuck, unassigned, or title keywords.'}
            action={<button type="button" className="btn btn-ghost btn-sm" onClick={() => setSearchQuery('')}>Clear search</button>}
          />
        </div>
      )}

      {!debouncedSearch && series.length === 0 && (
        <div className="card mb20">
          <EmptyState
            icon={<NavIcon name="Kanban" size={40} />}
            title="Your pipeline is empty"
            message="Start with the first series, then move it through cutting, QC, thumbnail, ready, and publish."
            action={!editorMode && can('pipeline', 'create') ? <button type="button" className="btn" onClick={() => setShowNew(true)}><NavIcon name="Plus" size={16} /> Create first series</button> : null}
          />
        </div>
      )}

      {!(debouncedSearch && series.length === 0) && series.length > 0 && (
        editorMode
          ? renderKanbanColumns(editorColumns)
          : (viewMode === 'kanban' ? renderKanbanColumns(STAGES) : renderSwimlanes())
      )}

      {detail && <SeriesDetail series={detail.series} initialTab={detail.tab} accounts={accounts} categories={categories} users={users}
        user={user}
        onClose={() => setDetail(null)}
        onUpdated={onSeriesUpdated}
        onSaved={() => { setDetail(null); load(); onChange && onChange(); }} />}
      {showNew && <NewSeriesModal accounts={accounts} categories={categories} users={users}
        onClose={() => setShowNew(false)} onCreated={() => { setShowNew(false); load(); onChange && onChange(); }} />}
    </div>
  );
}

function NewSeriesModal({ accounts, categories, users, onClose, onCreated }) {
  const [f, setF] = useState({ title: '', category: categories[0]?.name || '', account_id: '', assigned_user_id: '', source_link: '', description: '' });
  const [saving, setSaving] = useState(false);
  async function save() {
    if (!f.title) return toast('Title required');
    setSaving(true);
    try {
      await api('/series', { method: 'POST', body: { ...f, account_id: f.account_id || null } });
      toast('Series created'); onCreated();
    } catch (e) { toast(e.message); setSaving(false); }
  }
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><h2>New Series</h2><CloseButton onClick={onClose} /></div>
        <div className="modal-body">
          <div className="field"><label>Title</label><input value={f.title} onChange={e => setF({ ...f, title: e.target.value })} placeholder="Series title…" /></div>
          <div className="field"><label>Category</label>
            <select value={f.category} onChange={e => setF({ ...f, category: e.target.value })}>
              {categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
            </select>
          </div>
          <div className="field"><label>Target Account</label>
            <select value={f.account_id} onChange={e => {
              const nextAccountId = e.target.value;
              const nextAccount = accounts.find(account => account.id === nextAccountId);
              setF({ ...f, account_id: nextAccountId, category: nextAccount?.category || f.category });
            }}>
              <option value="">— none —</option>
              {accounts.map(a => <option key={a.id} value={a.id}>{a.name} (@{a.username})</option>)}
            </select>
          </div>
          <div className="field"><label>Assignee</label>
            <UserPicker users={users} value={f.assigned_user_id} onChange={value => setF({ ...f, assigned_user_id: value || '' })} />
          </div>
          <div className="field"><label>Source Link</label><input value={f.source_link} onChange={e => setF({ ...f, source_link: e.target.value })} placeholder="TikTok / YouTube URL…" /></div>
          <div className="field"><label>Description</label><textarea value={f.description} onChange={e => setF({ ...f, description: e.target.value })} /></div>
        </div>
        <div className="modal-foot">
          <button className="btn-ghost btn" onClick={onClose}>Cancel</button>
          <button className="btn" onClick={save} disabled={saving}>{saving ? 'Creating…' : 'Create Series'}</button>
        </div>
      </div>
    </div>
  );
}

function SeriesDetail({ series, initialTab, accounts, categories, users, user, onClose, onSaved, onUpdated }) {
  const [s, setS] = useState({ ...series });
  const [tab, setTab] = useState(initialTab || 'overview');
  const [dirty, setDirty] = useState(false);
  const [saving, setSaving] = useState(false);
  const [loading, setLoading] = useState(true);
  const [comments, setComments] = useState([]);
  const [activity, setActivity] = useState([]);
  const [commentBody, setCommentBody] = useState('');
  const [commentSaving, setCommentSaving] = useState(false);
  const [replyTo, setReplyTo] = useState(null);
  const isEditor = user?.role === 'editor';
  const account = accounts.find(item => item.id === s.account_id);
  const stageOptions = isEditor ? editorAllowedStages() : STAGES.map(st => st.id);
  const workspaceTabs = [
    { id: 'overview', label: 'Overview' },
    { id: 'files', label: 'Files' },
    { id: 'discussion', label: 'Discussion' },
    { id: 'activity', label: 'Activity' },
  ];

  const loadWorkspace = useCallback(async () => {
    setLoading(true);
    try {
      const [detail, nextComments, nextActivity] = await Promise.all([
        api(`/series/${series.id}`),
        api(`/series/${series.id}/comments`),
        api(`/series/${series.id}/activity`),
      ]);
      setS(detail);
      setComments(nextComments);
      setActivity(nextActivity);
      setDirty(false);
    } catch (e) {
      toast(e.message);
    }
    setLoading(false);
  }, [series.id]);

  useEffect(() => { loadWorkspace(); }, [loadWorkspace]);
  useEffect(() => { setTab(initialTab || 'overview'); }, [initialTab, series.id]);

  function setField(k, v) {
    setDirty(true);
    setS(prev => ({ ...prev, [k]: v }));
  }

  function applyUpdate(data) {
    setS(prev => ({ ...prev, ...data }));
    onUpdated && onUpdated(data);
  }

  async function save() {
    setSaving(true);
    try {
      const updated = await api(`/series/${s.id}`, { method: 'PUT', body: s });
      applyUpdate(updated);
      setDirty(false);
      toast('Saved');
    } catch (e) {
      toast(e.message);
    }
    setSaving(false);
  }

  async function del() {
    if (!confirm('Delete this series?')) return;
    await api(`/series/${s.id}`, { method: 'DELETE' });
    toast('Deleted');
    onSaved();
  }

  async function moveStage(stage) {
    if (s.stage === stage) return;
    try {
      const updated = await api(`/series/${s.id}/stage`, { method: 'PATCH', body: { stage } });
      applyUpdate(updated);
      toast(`Moved to ${stageLabel(stage)}`);
    } catch (e) {
      toast(e.message);
    }
  }

  async function postComment() {
    const body = commentBody.trim();
    if (!body) return;
    setCommentSaving(true);
    try {
      const created = await api(`/series/${s.id}/comments`, {
        method: 'POST',
        body: { body, parent_comment_id: replyTo?.id || null },
      });
      setComments(prev => [...prev, created]);
      setCommentBody('');
      setReplyTo(null);
      const nextActivity = await api(`/series/${s.id}/activity`);
      setActivity(nextActivity);
    } catch (e) {
      toast(e.message);
    }
    setCommentSaving(false);
  }

  function copySourceLink() {
    if (!s.source_link) return;
    navigator.clipboard.writeText(s.source_link)
      .then(() => toast('Link copied'))
      .catch(() => toast('Could not copy link'));
  }

  function openSourceLink() {
    if (!s.source_link) return;
    window.open(s.source_link, '_blank', 'noopener,noreferrer');
  }

  function insertMention(token) {
    setCommentBody(prev => `${prev}${prev && !prev.endsWith(' ') ? ' ' : ''}${token} `);
  }

  function renderCommentThread(comment, depth = 0) {
    const replies = comments.filter(item => item.parent_comment_id === comment.id);
    return (
      <div key={comment.id} className="comment-thread" style={{ marginRight: depth ? depth * 18 : 0 }}>
        <div className="comment-card">
          <div className="comment-head">
            <AssigneeAvatar
              userId={comment.author_user_id}
              name={comment.author_name}
              color={comment.author_color}
              avatarPath={comment.author_avatar_path}
              avatarUpdatedAt={comment.author_avatar_updated_at}
              meta={formatDateTime(comment.created_at)}
            />
            <button type="button" className="btn-ghost btn btn-sm" onClick={() => setReplyTo(comment)}>Reply</button>
          </div>
          <div className="comment-body">{renderCommentBody(comment.body)}</div>
        </div>
        {replies.length > 0 ? <div className="comment-replies">{replies.map(reply => renderCommentThread(reply, depth + 1))}</div> : null}
      </div>
    );
  }

  const topLevelComments = comments.filter(comment => !comment.parent_comment_id);

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal modal-workspace" onClick={e => e.stopPropagation()}>
        <div className="modal-head workspace-sticky-head">
          <div className="workspace-header-row">
            {isEditor ? (
              <h2 style={{ margin: 0, flex: 1, minWidth: 0 }}>{s.title}</h2>
            ) : (
              <input
                value={s.title}
                onChange={e => setField('title', e.target.value)}
                style={{ fontSize: 17, fontWeight: 700, background: 'transparent', border: 'none', color: 'var(--text)', flex: 1, minWidth: 140, padding: 0 }}
              />
            )}
            {can('pipeline', 'move_stage') && (
              <select className="workspace-stage-select" value={s.stage} onChange={e => moveStage(e.target.value)}>
                {stageOptions.map(id => (
                  <option key={id} value={id}>{stageLabel(id)}</option>
                ))}
              </select>
            )}
            {isEditor && s.stage !== 'qc' && (
              <button type="button" className="btn btn-sm" onClick={() => moveStage('qc')}>Mark Done</button>
            )}
          </div>
          <CloseButton onClick={onClose} />
        </div>
        <div className="modal-body">
          {loading ? <Spinner /> : (
            <>
              <div className="workspace-tabs">
                {workspaceTabs.map(item => (
                  <button key={item.id} type="button" className={'workspace-tab' + (tab === item.id ? ' active' : '')} onClick={() => setTab(item.id)}>
                    {item.label}
                  </button>
                ))}
              </div>

              {tab === 'overview' && (
                <div className="surface-card">
                  <div className="workspace-summary-grid">
                    <div className="field">
                      <label>Assignee</label>
                      {isEditor ? (
                        <div className="workspace-readonly">
                          <AssigneeAvatar
                            userId={s.assigned_user_id}
                            name={s.assignee_name}
                            color={s.assignee_color}
                            avatarPath={s.assignee_avatar_path}
                            avatarUpdatedAt={s.assignee_avatar_updated_at}
                          />
                        </div>
                      ) : (
                        <UserPicker users={users} value={s.assigned_user_id} onChange={value => setField('assigned_user_id', value)} />
                      )}
                    </div>
                    <div className="field">
                      <label>Account</label>
                      {isEditor ? (
                        <div className="workspace-readonly">{account ? `${account.name} (@${account.username})` : 'No account assigned'}</div>
                      ) : (
                        <select value={s.account_id || ''} onChange={e => setField('account_id', e.target.value || null)}>
                          <option value="">— none —</option>
                          {accounts.map(item => <option key={item.id} value={item.id}>{item.name} (@{item.username})</option>)}
                        </select>
                      )}
                    </div>
                    <div className="field">
                      <label>Category</label>
                      {isEditor ? (
                        <div className="workspace-readonly">{s.category || '—'}</div>
                      ) : (
                        <select value={s.category || ''} onChange={e => setField('category', e.target.value)}>
                          {categories.map(category => <option key={category.name} value={category.name}>{category.name}</option>)}
                        </select>
                      )}
                    </div>
                    <div className="field">
                      <label>Episodes</label>
                      {isEditor ? (
                        <div className="workspace-readonly">{s.episode_count || 0}</div>
                      ) : (
                        <input type="number" value={s.episode_count || 0} onChange={e => setField('episode_count', parseInt(e.target.value, 10) || 0)} />
                      )}
                    </div>
                  </div>
                  <div className="field">
                    <label>Source Link</label>
                    <div className="workspace-link-row">
                      <input value={s.source_link || ''} onChange={e => setField('source_link', e.target.value)} readOnly={isEditor} />
                      <button type="button" className="btn-ghost btn btn-sm" onClick={copySourceLink}><NavIcon name="Copy" size={14} /> Copy</button>
                      <button type="button" className="btn-ghost btn btn-sm" onClick={openSourceLink}><NavIcon name="ExternalLink" size={14} /> Open</button>
                    </div>
                  </div>
                  <div className="field">
                    <label>Hook / Trailer Notes</label>
                    <textarea
                      value={s.episode1_hook || ''}
                      onChange={e => setField('episode1_hook', e.target.value)}
                      placeholder="Opening beat… / the promise… / the tease…"
                      readOnly={isEditor}
                    />
                  </div>
                  <div className="field" style={{ marginBottom: 0 }}>
                    <label>Internal Notes</label>
                    <textarea value={s.notes || ''} onChange={e => setField('notes', e.target.value)} readOnly={isEditor} />
                  </div>
                </div>
              )}

              {tab === 'files' && (
                <div className="surface-card">
                  <SectionTitle icon={<NavIcon name="Image" size={14} />}>Cover</SectionTitle>
                  <SeriesCover series={s} onUploaded={applyUpdate} />
                  <div style={{ marginTop: 20 }}>
                    <SectionTitle icon={<NavIcon name="Video" size={14} />}>Parts</SectionTitle>
                    <SeriesPartsManager series={s} onChanged={applyUpdate} />
                  </div>
                </div>
              )}

              {tab === 'discussion' && (
                <div className="surface-card">
                  {topLevelComments.length === 0 ? <p className="workspace-muted">No comments yet.</p> : topLevelComments.map(comment => renderCommentThread(comment))}
                  <div className="comment-composer">
                    {replyTo ? (
                      <div className="comment-replying">
                        Replying to <b>{replyTo.author_name}</b>
                        <button type="button" className="btn-ghost btn btn-sm" onClick={() => setReplyTo(null)}>Cancel</button>
                      </div>
                    ) : null}
                    <textarea value={commentBody} onChange={e => setCommentBody(e.target.value)} placeholder="Write a comment or note…" />
                    <MentionPicker users={users} onPick={insertMention} />
                    <div className="comment-actions">
                      <button type="button" className="btn" onClick={postComment} disabled={commentSaving}>{commentSaving ? 'Posting…' : 'Post comment'}</button>
                    </div>
                  </div>
                </div>
              )}

              {tab === 'activity' && (
                <div className="surface-card">
                  {activity.length === 0 ? <p className="workspace-muted">No activity yet.</p> : (
                    <div className="workspace-activity-list">
                      {activity.map(item => (
                        <div key={item.id} className="workspace-activity-item">
                          <div>{item.message}</div>
                          <div className="workspace-activity-meta">{item.actor_name || 'System'} · {formatDateTime(item.created_at)}</div>
                        </div>
                      ))}
                    </div>
                  )}
                </div>
              )}
            </>
          )}
        </div>
        <div className="modal-foot">
          {!isEditor && can('pipeline', 'delete') && <button className="btn-danger btn" onClick={del}>Delete</button>}
          <button className="btn-ghost btn" onClick={onClose}>Close</button>
          {!isEditor && <button className="btn" onClick={save} disabled={saving || !dirty}>{saving ? 'Saving…' : 'Save changes'}</button>}
        </div>
      </div>
    </div>
  );
}

function FilesBrowser({ categories }) {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);
  const [filters, setFilters] = useState({ stage: '', category: '', account_id: '' });
  const [search, setSearch] = useState('');
  const [expanded, setExpanded] = useState({});
  const [previews, setPreviews] = useState({});

  const load = useCallback(async () => {
    setLoading(true);
    try {
      setItems(await api('/series/files'));
    } catch (e) {
      toast(e.message);
    }
    setLoading(false);
  }, []);

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

  const accountOptions = Array.from(new Map(
    items
      .filter(item => item.account_id)
      .map(item => [item.account_id, { id: item.account_id, name: item.account_name || item.account_username || 'Unknown account' }])
  ).values());

  const filtered = items.filter(item => {
    if (filters.stage && item.stage !== filters.stage) return false;
    if (filters.category && item.category !== filters.category) return false;
    if (filters.account_id && item.account_id !== filters.account_id) return false;
    if (!search.trim()) return true;
    const q = search.trim().toLowerCase();
    return [
      item.title,
      item.storage_folder_name,
      item.category,
      item.account_name,
      item.account_username,
      stageLabel(item.stage),
    ].filter(Boolean).some(value => String(value).toLowerCase().includes(q));
  });

  const visibleFiles = filtered.reduce((sum, item) => sum + Number(item.total_file_count || 0), 0);

  function ensurePreview(item) {
    const firstPart = item.parts?.find(part => part.is_available) || item.parts?.[0] || null;
    return item.cover
      ? { type: 'cover' }
      : firstPart
        ? { type: 'part', partId: firstPart.id }
        : null;
  }

  function toggleExpand(item) {
    setExpanded(prev => ({ ...prev, [item.id]: !prev[item.id] }));
    setPreviews(prev => {
      if (prev[item.id]) return prev;
      const next = ensurePreview(item);
      return next ? { ...prev, [item.id]: next } : prev;
    });
  }

  function setPreview(item, preview) {
    setExpanded(prev => ({ ...prev, [item.id]: true }));
    setPreviews(prev => ({ ...prev, [item.id]: preview }));
  }

  function renderPreview(item) {
    const preview = previews[item.id] || ensurePreview(item);
    if (!preview) return <div className="files-preview-empty">No local files in this folder yet.</div>;

    if (preview.type === 'cover' && item.cover) {
      return (
        <img
          className="files-preview-image"
          src={`${API}/series/${item.id}/cover?v=${encodeURIComponent(item.updated_at || item.cover.file_name || '')}`}
          alt=""
        />
      );
    }

    const activePart = item.parts.find(part => part.id === preview.partId) || item.parts[0];
    if (!activePart?.is_available) return <div className="files-preview-empty">This file is no longer stored locally.</div>;
    return (
      <video
        key={activePart.id}
        className="files-preview-video"
        controls
        preload="metadata"
        src={`${API}/series/${item.id}/parts/${activePart.id}/stream`}
      />
    );
  }

  return (
    <div>
      <PageHeader
        eyebrow="Library"
        title="Files & Media"
        subtitle="Browse series folders, preview covers and parts, and download files without opening each series card."
        meta={
          <>
            <ContextPill label="Series folders" value={filtered.length} />
            <ContextPill label="Visible files" value={visibleFiles} tone="info" />
          </>
        }
      />

      <div className="card filter-panel surface-card mb20">
        <FilterBar>
          <div className="pipeline-search">
            <span className="pipeline-search-icon"><NavIcon name="Search" size={18} /></span>
            <input
              type="search"
              value={search}
              onChange={e => setSearch(e.target.value)}
              placeholder="Search by title, folder, account, or stage…"
              aria-label="Search files"
            />
            {search && (
              <button type="button" className="pipeline-search-clear" onClick={() => setSearch('')} aria-label="Clear search">
                <NavIcon name="X" size={16} />
              </button>
            )}
          </div>
          <select value={filters.stage} onChange={e => setFilters({ ...filters, stage: e.target.value })}>
            <option value="">All Stages</option>
            {STAGES.map(stage => <option key={stage.id} value={stage.id}>{stage.label}</option>)}
          </select>
          <select value={filters.category} onChange={e => setFilters({ ...filters, category: e.target.value })}>
            <option value="">All Categories</option>
            {categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
          </select>
          <select value={filters.account_id} onChange={e => setFilters({ ...filters, account_id: e.target.value })}>
            <option value="">All Accounts</option>
            {accountOptions.map(account => <option key={account.id} value={account.id}>{account.name}</option>)}
          </select>
        </FilterBar>
      </div>

      {loading ? <Spinner /> : filtered.length === 0 ? (
        <div className="card surface-card">
          <EmptyState
            icon={<NavIcon name="FileText" size={40} />}
            title="No folders match these filters"
            message="Try another stage, account, category, or search term."
          />
        </div>
      ) : (
        <div className="files-browser-list">
          {filtered.map(item => {
            const isOpen = !!expanded[item.id];
            return (
              <div key={item.id} className="card files-browser-card">
                <div className="files-browser-head">
                  <div className="files-browser-copy">
                    <div className="files-browser-title-row">
                      <h3>{item.title}</h3>
                      <div className="files-browser-badges">
                        <Badge color={stageColor(item.stage)}>{stageLabel(item.stage)}</Badge>
                        <Badge color={categoryColor(categories, item.category)}>{item.category}</Badge>
                      </div>
                    </div>
                    <div className="files-browser-subline">{item.storage_folder_name}</div>
                    <div className="files-browser-meta">
                      <span>{item.account_name ? `${item.account_name}${item.account_username ? ` · @${item.account_username}` : ''}` : 'No account assigned'}</span>
                      <span>{item.parts_count} part{item.parts_count !== 1 ? 's' : ''}</span>
                      <span>{item.has_cover ? 'Cover ready' : 'No cover'}</span>
                    </div>
                    <div className="files-browser-foot">
                      <AssigneeAvatar
                        userId={item.assigned_user_id}
                        name={item.assignee_name}
                        color={item.assignee_color}
                        avatarPath={item.assignee_avatar_path}
                        avatarUpdatedAt={item.assignee_avatar_updated_at}
                      />
                      {item.has_pending_cleanup && (
                        <span className="files-cleanup-pill">
                          Cleanup {formatDateTime(item.media_cleanup_due_at)}
                        </span>
                      )}
                    </div>
                  </div>

                  <div className="files-browser-actions">
                    {item.has_files && (
                      <button type="button" className="btn-ghost btn btn-sm" onClick={() => triggerDownload(`${API}/series/${item.id}/files/download`, `${item.storage_folder_name || item.title || 'series'}.zip`)}>
                        <NavIcon name="Download" size={14} /> Download ZIP
                      </button>
                    )}
                    <button type="button" className="btn btn-sm" onClick={() => toggleExpand(item)}>
                      {isOpen ? 'Hide Files' : 'Show Files'}
                    </button>
                  </div>
                </div>

                {isOpen && (
                  <div className="files-browser-body">
                    <div className="files-preview-card">
                      {renderPreview(item)}
                    </div>

                    <div className="files-list">
                      {item.cover && (
                        <div className="files-list-row">
                          <div className="files-list-main">
                            <div className="files-list-title">Cover</div>
                            <div className="files-list-meta">{item.cover.file_name}</div>
                          </div>
                          <div className="files-list-actions">
                            <button type="button" className="btn-ghost btn btn-sm" onClick={() => setPreview(item, { type: 'cover' })}>
                              Preview
                            </button>
                            <button type="button" className="btn-ghost btn btn-sm" onClick={() => triggerDownload(`${API}/series/${item.id}/cover/download`, item.cover?.file_name || 'cover.jpg')}>
                              <NavIcon name="Download" size={14} /> Download
                            </button>
                          </div>
                        </div>
                      )}

                      {item.parts.map(part => (
                        <div key={part.id} className={'files-list-row' + (!part.is_available ? ' unavailable' : '')}>
                          <div className="files-list-main">
                            <div className="files-list-title">Part {part.part_number || part.episode_number || '?'}</div>
                            <div className="files-list-meta">
                              {part.file_name || 'No file'}
                              {part.file_size ? ` • ${formatBytes(part.file_size)}` : ''}
                              {part.duration ? ` • ${Math.round(part.duration)}s` : ''}
                              {!part.is_available ? ' • local file removed' : ''}
                            </div>
                          </div>
                          <div className="files-list-actions">
                            <button type="button" className="btn-ghost btn btn-sm" disabled={!part.is_available} onClick={() => setPreview(item, { type: 'part', partId: part.id })}>
                              Preview
                            </button>
                            {part.is_available && (
                              <button type="button" className="btn-ghost btn btn-sm" onClick={() => triggerDownload(`${API}/series/${item.id}/parts/${part.id}/download`, part.file_name || `part-${part.part_number || part.episode_number || 'file'}.mp4`)}>
                                <NavIcon name="Download" size={14} /> Download
                              </button>
                            )}
                          </div>
                        </div>
                      ))}

                      {!item.cover && !item.parts.length && (
                        <div className="files-preview-empty">No local files in this folder yet.</div>
                      )}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 3 — ACCOUNTS
// ════════════════════════════════════════════════════════════
function Accounts({ categories }) {
  const [accounts, setAccounts] = useState([]);
  const [showNew, setShowNew] = useState(false);
  const [showImport, setShowImport] = useState(false);
  const [detail, setDetail] = useState(null);

  const load = useCallback(async () => setAccounts(await api('/accounts')), []);
  useEffect(() => { load(); }, [load]);

  return (
    <div>
      <PageHeader
        eyebrow="Content Ops"
        title="Accounts"
        subtitle="Compare account workload, identity, and output at a glance."
        meta={<ContextPill label="Accounts" value={accounts.length} />}
        actions={
        <>
          {can('accounts', 'export') && <button type="button" className="btn-ghost btn" onClick={() => exportExcel('/export/accounts', 'redz-accounts.xlsx')}>Export Excel</button>}
          {can('accounts', 'create') && <button type="button" className="btn-ghost btn" onClick={() => setShowImport(true)}>Import Excel</button>}
          {can('accounts', 'create') && <button type="button" className="btn" onClick={() => setShowNew(true)}><NavIcon name="Plus" size={16} /> Add Account</button>}
        </>
      }
      />
      {accounts.length === 0 ? (
        <div className="card surface-card">
          <EmptyState
            icon={<NavIcon name="Smartphone" size={40} />}
            title="No accounts added yet"
            message="Create or import publishing accounts so the pipeline can assign work to real destinations."
            action={can('accounts', 'create') ? <button type="button" className="btn" onClick={() => setShowNew(true)}><NavIcon name="Plus" size={16} /> Add Account</button> : null}
          />
        </div>
      ) : (
      <div className="grid acct-grid">
        {accounts.map(a => (
          <div key={a.id} className="acct-card" onClick={() => setDetail(a)}>
            <div className="acct-top">
              <div className="acct-pfp" style={{ background: a.pfp_color || '#E63946' }}>{a.name?.[0]}</div>
              <div style={{ flex: 1 }}>
                <div className="acct-name">{a.name}</div>
                <div className="acct-user">@{a.username}</div>
              </div>
              <Badge color={a.status === 'active' ? '#2ECC71' : '#8888A8'}>{a.status}</Badge>
            </div>
            <div className="acct-bio">{a.bio}</div>
            <div className="acct-stats">
              <div className="acct-stat"><b>{a.published_count}</b>Published</div>
              <div className="acct-stat"><b>{a.pipeline_count}</b>In Pipeline</div>
              <div className="acct-stat" style={{ marginLeft: 'auto', textAlign: 'right' }}><span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{a.category}</span></div>
            </div>
          </div>
        ))}
      </div>
      )}
      {showNew && <AccountModal categories={categories} onClose={() => setShowNew(false)} onSaved={() => { setShowNew(false); load(); }} />}
      {detail && <AccountModal account={detail} categories={categories} onClose={() => setDetail(null)} onSaved={() => { setDetail(null); load(); }} />}
      {showImport && <ImportModal type="accounts" onClose={() => setShowImport(false)} onDone={() => { setShowImport(false); load(); }} />}
    </div>
  );
}

function AccountModal({ account, categories, onClose, onSaved }) {
  const [f, setF] = useState(account || { name: '', username: '', category: categories[0]?.name || '', bio: '', pfp_description: '', pfp_color: '#E63946', status: 'active' });
  const [saving, setSaving] = useState(false);
  async function save() {
    if (!f.name || !f.username) return toast('Name and username required');
    setSaving(true);
    try {
      if (account) await api(`/accounts/${account.id}`, { method: 'PUT', body: f });
      else await api('/accounts', { method: 'POST', body: f });
      toast('Saved'); onSaved();
    } catch (e) { toast(e.message); setSaving(false); }
  }
  async function del() {
    if (!confirm('Delete this account?')) return;
    await api(`/accounts/${account.id}`, { method: 'DELETE' }); toast('Deleted'); onSaved();
  }
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><h2>{account ? 'Edit Account' : 'New Account'}</h2><CloseButton onClick={onClose} /></div>
        <div className="modal-body">
          <div className="field-row">
            <div className="field"><label>Account Name (Arabic)</label><input dir="rtl" value={f.name} onChange={e => setF({ ...f, name: e.target.value })} /></div>
            <div className="field"><label>Username</label><input value={f.username} onChange={e => setF({ ...f, username: e.target.value })} /></div>
          </div>
          <div className="field-row">
            <div className="field"><label>Category</label>
              <select value={f.category} onChange={e => setF({ ...f, category: e.target.value })}>
                {categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
              </select>
            </div>
            <div className="field"><label>Status</label>
              <select value={f.status} onChange={e => setF({ ...f, status: e.target.value })}>
                <option value="active">Active</option><option value="paused">Paused</option>
              </select>
            </div>
          </div>
          <div className="field"><label>Bio (Arabic)</label><textarea dir="rtl" value={f.bio} onChange={e => setF({ ...f, bio: e.target.value })} /></div>
          <div className="field"><label>Profile Picture Description</label><textarea value={f.pfp_description} onChange={e => setF({ ...f, pfp_description: e.target.value })} /></div>
          <div className="field"><label>Accent Color</label><input type="color" value={f.pfp_color} onChange={e => setF({ ...f, pfp_color: e.target.value })} style={{ height: 42, padding: 4 }} /></div>
        </div>
        <div className="modal-foot">
          {account && <button className="btn-danger btn" onClick={del}>Delete</button>}
          <button className="btn-ghost btn" onClick={onClose}>Cancel</button>
          <button className="btn" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
        </div>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 4 — RESEARCH & IDEAS
// ════════════════════════════════════════════════════════════
function Research({ onChange }) {
  const [accounts, setAccounts] = useState([]);
  const [single, setSingle] = useState({ title: '', account_id: '', source_link: '' });
  const [savingSingle, setSavingSingle] = useState(false);
  const [showImport, setShowImport] = useState(false);

  const load = useCallback(async () => {
    setAccounts(await api('/accounts'));
  }, []);
  useEffect(() => { load(); }, [load]);

  async function submitSingle(e) {
    e.preventDefault();
    if (!single.title.trim() || !single.account_id || !single.source_link.trim()) return toast('Title, account, and source link are required');
    setSavingSingle(true);
    try {
      await api('/ideas', { method: 'POST', body: single });
      setSingle({ title: '', account_id: '', source_link: '' });
      toast('Idea added directly to pipeline');
      onChange && onChange();
    } catch (e2) {
      toast(e2.message);
      setSavingSingle(false);
      return;
    }
    setSavingSingle(false);
  }

  return (
    <div>
      <PageHeader
        eyebrow="Intake"
        title="Ideas"
        subtitle="Add single ideas or upload a bulk sheet to create pipeline cards immediately at the `idea` stage."
        meta={
          <>
            <ContextPill label="Accounts" value={accounts.length} />
            <ContextPill label="Category source" value="Selected account" tone="info" />
          </>
        }
      />

      <div className="grid grid-2">
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="Plus" size={14} />}>Add Idea</SectionTitle>
          <form onSubmit={submitSingle}>
            <div className="field">
              <label>Title</label>
              <input value={single.title} onChange={e => setSingle({ ...single, title: e.target.value })} placeholder="Series title…" />
            </div>
            <div className="field">
              <label>Account</label>
              <select value={single.account_id} onChange={e => setSingle({ ...single, account_id: e.target.value })}>
                <option value="">Select account…</option>
                {accounts.map(account => (
                  <option key={account.id} value={account.id}>
                    {account.name} (@{account.username}) {account.category ? `· ${account.category}` : ''}
                  </option>
                ))}
              </select>
            </div>
            <div className="field">
              <label>Source Link</label>
              <input value={single.source_link} onChange={e => setSingle({ ...single, source_link: e.target.value })} placeholder="https://..." />
            </div>
            <button type="submit" className="btn" disabled={savingSingle}>
              {savingSingle ? 'Adding…' : 'Add Idea'}
            </button>
          </form>
        </div>

        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="FileText" size={14} />}>Bulk Add Ideas</SectionTitle>
          <p style={{ fontSize: 12.5, color: 'var(--text-dim)', marginBottom: 12 }}>
            Upload an Excel or CSV sheet with these columns: <b>title</b>, <b>link</b>, and <b>account</b>.
          </p>
          <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
            <button type="button" className="btn" onClick={() => setShowImport(true)}>
              <NavIcon name="Upload" size={16} /> Upload Sheet
            </button>
            <button type="button" className="btn-ghost btn" onClick={() => exportExcel('/export/ideas-template', 'redz-ideas-template.xlsx')}>
              <NavIcon name="Download" size={16} /> Download Sample Template
            </button>
          </div>
          <p style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 12 }}>
            Use the template if you want the exact format. Account can be the account name, username, or account ID.
          </p>
        </div>
      </div>
      {showImport && <ImportModal type="ideas" onClose={() => setShowImport(false)} onDone={() => { setShowImport(false); onChange && onChange(); }} />}
    </div>
  );
}

function IdeaModal({ categories, accounts, onClose, onSaved }) {
  const [f, setF] = useState({ title: '', source_link: '', views: '', suggested_category: '', suggested_account: '', ai_score: '' });
  async function save() {
    await api('/ideas', { method: 'POST', body: f }); toast('Idea added'); onSaved();
  }
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><h2>New Idea</h2><CloseButton onClick={onClose} /></div>
        <div className="modal-body">
          <div className="field"><label>Title / Description</label><input value={f.title} onChange={e => setF({ ...f, title: e.target.value })} /></div>
          <div className="field"><label>Source Link</label><input value={f.source_link} onChange={e => setF({ ...f, source_link: e.target.value })} /></div>
          <div className="field-row">
            <div className="field"><label>Views</label><input value={f.views} onChange={e => setF({ ...f, views: e.target.value })} placeholder="500K" /></div>
            <div className="field"><label>AI Score (1-10)</label><input type="number" value={f.ai_score} onChange={e => setF({ ...f, ai_score: e.target.value })} /></div>
          </div>
          <div className="field-row">
            <div className="field"><label>Category</label>
              <select value={f.suggested_category} onChange={e => setF({ ...f, suggested_category: e.target.value })}>
                <option value="">—</option>{categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
              </select>
            </div>
          </div>
        </div>
        <div className="modal-foot">
          <button className="btn-ghost btn" onClick={onClose}>Cancel</button>
          <button className="btn" onClick={save}>Add Idea</button>
        </div>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 5 — AI STUDIO
// ════════════════════════════════════════════════════════════
function AIStudio({ categories }) {
  const [tab, setTab] = useState('script');
  const tabs = [
    { id: 'script', label: 'Script Generator', icon: 'PenLine' },
    { id: 'thumbnail', label: 'Thumbnail Prompt', icon: 'Image' },
    { id: 'caption', label: 'Caption & Title', icon: 'MessageCircle' },
  ];
  return (
    <div>
      <PageHeader
        eyebrow="Creative Tools"
        title="AI Studio"
        subtitle="Generate production-ready writing assets without leaving the REDZ workflow."
      />
      <div className="tabs">
        {tabs.map(t => (
          <button key={t.id} type="button" className={'tab' + (tab === t.id ? ' active' : '')} onClick={() => setTab(t.id)}>
            <NavIcon name={t.icon} size={14} /> {t.label}
          </button>
        ))}
      </div>
      {tab === 'script' && <AITool
        title="Script Generator"
        hint="Generates a 3-episode Arabic voiceover script with the Episode 1 trailer formula built in. Best for Track B."
        endpoint="/ai/script" categories={categories}
        fields={[{ k: 'topic', label: 'Topic / Story Idea', type: 'textarea' }, { k: 'category', label: 'Category', type: 'category' }]}
        rtl />}
      {tab === 'thumbnail' && <AITool
        title="Thumbnail Prompt Generator"
        hint="Generates a detailed image prompt to paste into ChatGPT / DALL-E. Strong expression, max 5 Arabic words, high contrast."
        endpoint="/ai/thumbnail-prompt" categories={categories}
        fields={[{ k: 'title', label: 'Series Title' }, { k: 'moment', label: 'Key Visual Moment / Emotion' }, { k: 'category', label: 'Category', type: 'category' }]} />}
      {tab === 'caption' && <AITool
        title="Caption & Title Generator"
        hint="Generates a Series title + posting caption in pan-Arab Arabic using سيريز, with a unique CTA."
        endpoint="/ai/caption" categories={categories}
        fields={[{ k: 'title', label: 'Series Title' }, { k: 'platform', label: 'Platform', type: 'platform' }]}
        rtl />}
    </div>
  );
}

function AITool({ title, hint, endpoint, fields, categories, rtl }) {
  const [vals, setVals] = useState({});
  const [out, setOut] = useState('');
  const [loading, setLoading] = useState(false);
  async function gen() {
    setLoading(true); setOut('');
    try {
      const r = await api(endpoint, { method: 'POST', body: vals });
      setOut(r.result || r.script || r.prompt || r.caption || JSON.stringify(r));
    } catch (e) { setOut('Error: ' + e.message + '\n\nMake sure your Anthropic API key is set in Settings.'); }
    setLoading(false);
  }
  function copy() { navigator.clipboard.writeText(out); toast('Copied'); }
  return (
    <div className="card surface-card">
      <div className="section-title">{title}</div>
      <p style={{ fontSize: 12.5, color: 'var(--text-dim)', marginBottom: 16 }}>{hint}</p>
      {fields.map(f => (
        <div className="field" key={f.k}>
          <label>{f.label}</label>
          {f.type === 'textarea' ? <textarea value={vals[f.k] || ''} onChange={e => setVals({ ...vals, [f.k]: e.target.value })} />
            : f.type === 'category' ? <select value={vals[f.k] || ''} onChange={e => setVals({ ...vals, [f.k]: e.target.value })}><option value="">—</option>{categories.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}</select>
            : f.type === 'platform' ? <select value={vals[f.k] || ''} onChange={e => setVals({ ...vals, [f.k]: e.target.value })}><option value="">—</option>{['Instagram', 'TikTok', 'YouTube', 'Facebook'].map(p => <option key={p} value={p}>{p}</option>)}</select>
            : <input value={vals[f.k] || ''} onChange={e => setVals({ ...vals, [f.k]: e.target.value })} />}
        </div>
      ))}
      <button type="button" className="btn" onClick={gen} disabled={loading}>
        {loading ? 'Generating…' : <><NavIcon name="Sparkles" size={16} /> Generate</>}
      </button>
      {loading && <Spinner />}
      {out && <div className={'ai-output' + (rtl ? '' : ' ltr')}>{out}<div style={{ marginTop: 12 }}><button type="button" className="btn-ghost btn btn-sm" onClick={copy}><NavIcon name="Copy" size={14} /> Copy</button></div></div>}
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 6 — QUALITY CONTROL
// ════════════════════════════════════════════════════════════
function QualityControl({ categories, onChange }) {
  const [items, setItems] = useState([]);
  const load = useCallback(async () => setItems(await api('/series?stage=qc')), []);
  useEffect(() => { load(); }, [load]);

  return (
    <div>
      <PageHeader
        eyebrow="Review"
        title="Quality Control"
        subtitle="Review portrait parts, leave notes when needed, and move approved series forward."
        meta={<ContextPill label="Awaiting QC" value={items.length} tone={items.length ? 'warning' : 'success'} />}
      />
      {items.length === 0 && (
        <div className="card surface-card">
          <EmptyState
            icon={<NavIcon name="CheckCircle" size={40} />}
            title="QC queue is clear"
            message="All series have passed review."
          />
        </div>
      )}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {items.map(s => <QCCard key={s.id} series={s} categories={categories} onDone={() => { load(); onChange && onChange(); }} />)}
      </div>
    </div>
  );
}

function QCCard({ series, categories, onDone }) {
  const [reason, setReason] = useState('');
  async function approve() {
    await api(`/series/${series.id}`, { method: 'PUT', body: { ...series, stage: 'thumbnail' } });
    toast('Approved → Thumbnail'); onDone();
  }
  async function sendBack() {
    const feedback = reason.trim() ? `[QC] ${reason.trim()}` : '';
    const nextNotes = [series.notes, feedback].filter(Boolean).join('\n');
    if (feedback) {
      await api(`/series/${series.id}/comments`, { method: 'POST', body: { body: feedback } });
    }
    await api(`/series/${series.id}`, { method: 'PUT', body: { ...series, stage: 'cutting', notes: nextNotes } });
    toast('Sent back to Cutting'); onDone();
  }
  return (
    <div className="card surface-card qc-card">
      <div className="qc-card-head">
        <div>
          <h3 className="qc-card-title">{series.title}</h3>
          <div className="qc-card-meta">
            <Badge color={categoryColor(categories, series.category)}>{series.category}</Badge>
            <span className="qc-card-account">{series.account_name}</span>
          </div>
        </div>
      </div>
      <div className="qc-review-grid">
        <div className="qc-review-main">
          <QCPartsPlayer seriesId={series.id} />
          {series.episode1_hook && <div className="qc-hook-box">{series.episode1_hook}</div>}
        </div>

        <div className="qc-review-side">
          <div className="qc-summary-card">
            <div className="qc-side-label">Review focus</div>
            <div className="qc-summary-text">
              Watch the ordered parts, confirm the cut is ready, and send it back to Cutting with clear notes if anything still needs work.
            </div>
          </div>

          {series.notes && (
            <div className="qc-existing-notes">
              <div className="qc-side-label">Current notes</div>
              <div className="qc-existing-notes-body">{series.notes}</div>
            </div>
          )}

          <div className="qc-actions qc-actions-sticky">
            <div className="qc-side-label">Send-back notes</div>
            <textarea
              value={reason}
              onChange={e => setReason(e.target.value)}
              placeholder="Reason to send back…"
              className="qc-reason-input"
            />
            <div className="qc-actions-row qc-actions-row-fixed">
              <button type="button" className="btn btn-green" onClick={approve}>
                <NavIcon name="Check" size={16} /> Approve to Thumbnail
              </button>
              <button type="button" className="btn-ghost btn" onClick={sendBack}>
                <NavIcon name="ChevronLeft" size={16} /> Back to Cutting
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 7 — PERFORMANCE
// ════════════════════════════════════════════════════════════
function Performance({ categories, settings }) {
  const [data, setData] = useState(null);
  const [entry, setEntry] = useState({ series_id: '', ep1_views: '', completion_rate: '', join_rate: '', comments: '' });
  const [publishedSeries, setPublishedSeries] = useState([]);

  const load = useCallback(async () => {
    try {
      setData(await api('/performance'));
      setPublishedSeries(await api('/series?stage=published'));
    } catch (e) { toast(e.message); }
  }, []);
  useEffect(() => { load(); }, [load]);

  async function submit() {
    if (!entry.series_id) return toast('Pick a series');
    await api('/performance', { method: 'POST', body: entry });
    toast('Performance recorded'); setEntry({ series_id: '', ep1_views: '', completion_rate: '', join_rate: '', comments: '' });
    load();
  }
  async function briefMore(seriesId, title, category) {
    await api(`/performance/${seriesId}/amplify`, { method: 'POST', body: { title, category } });
    toast('5 new ideas added to research inbox'); 
  }

  if (!data) return <Spinner />;
  const threshold = parseInt(settings?.performance_threshold) || data.threshold || 10000;

  const byCategoryList = Object.entries(data.byCategory || {}).map(([category, v]) => ({ category, wins: v.winners, total: v.total }));
  const byAccountList = Object.entries(data.byAccount || {}).map(([name, v]) => ({ name, wins: v.winners, total: v.total }));
  return (
    <div>
      <PageHeader
        eyebrow="Outcomes"
        title="Performance"
        subtitle="Track what wins, what underperforms, and which formats deserve another brief."
        meta={
          <>
            <ContextPill label="Winner threshold" value={threshold.toLocaleString()} />
            <ContextPill label="Published tracked" value={publishedSeries.length} />
          </>
        }
      />
      {/* Entry form */}
      <div className="card mb20">
        <SectionTitle style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span>Record Performance (manual entry)</span>
          {can('performance', 'export') && <button type="button" className="btn-ghost btn btn-sm" onClick={() => exportExcel('/export/performance', 'redz-performance.xlsx')}>Export Excel</button>}
        </SectionTitle>
        <FilterBar>
          <div className="field" style={{ flex: 2, minWidth: 200, marginBottom: 0 }}>
            <label>Series</label>
            <select value={entry.series_id} onChange={e => setEntry({ ...entry, series_id: e.target.value })}>
              <option value="">— pick published series —</option>
              {publishedSeries.map(s => <option key={s.id} value={s.id}>{s.title}</option>)}
            </select>
          </div>
          <div className="field" style={{ flex: 1, minWidth: 110, marginBottom: 0 }}><label>Ep1 Views</label><input type="number" value={entry.ep1_views} onChange={e => setEntry({ ...entry, ep1_views: e.target.value })} /></div>
          <div className="field" style={{ flex: 1, minWidth: 100, marginBottom: 0 }}><label>Completion %</label><input type="number" value={entry.completion_rate} onChange={e => setEntry({ ...entry, completion_rate: e.target.value })} /></div>
          <div className="field" style={{ flex: 1, minWidth: 90, marginBottom: 0 }}><label>Joins</label><input type="number" value={entry.join_rate} onChange={e => setEntry({ ...entry, join_rate: e.target.value })} /></div>
          <div className="field" style={{ flex: 1, minWidth: 90, marginBottom: 0 }}><label>Comments</label><input type="number" value={entry.comments} onChange={e => setEntry({ ...entry, comments: e.target.value })} /></div>
          <button type="button" className="btn" onClick={submit}>Record</button>
        </FilterBar>
        <p style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 10 }}>Winner threshold: {threshold.toLocaleString()} Ep1 views (change in Settings)</p>
      </div>

      {/* Winners + Underperformers */}
      <div className="grid grid-2 mb20">
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="Trophy" size={14} />} style={{ color: 'var(--success)' }}>Winners</SectionTitle>
          {data.winners.length === 0 && <p style={{ fontSize: 13, color: 'var(--text-muted)' }}>No winners yet.</p>}
          {data.winners.map(w => (
            <div key={w.id} className="perf-row">
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{w.title}</div>
                <div style={{ fontSize: 11.5, color: 'var(--success)' }}>{Number(w.performance?.ep1_views || 0).toLocaleString()} views · {w.category}</div>
              </div>
              <button className="btn btn-sm" onClick={() => briefMore(w.id, w.title, w.category)}>Brief 5 More →</button>
            </div>
          ))}
        </div>
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="TrendingDown" size={14} />} style={{ color: 'var(--primary)' }}>Underperformers</SectionTitle>
          {data.underperformers.length === 0 && <p style={{ fontSize: 13, color: 'var(--text-muted)' }}>None flagged.</p>}
          {data.underperformers.map(u => (
            <div key={u.id} className="perf-row">
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{u.title}</div>
                <div style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>{Number(u.performance?.ep1_views || 0).toLocaleString()} views · {u.category}</div>
              </div>
              <span className="stuck">avoid format</span>
            </div>
          ))}
        </div>
      </div>

      {/* Breakdown */}
      <div className="grid grid-2">
        <div className="card surface-card">
          <div className="section-title">By Category</div>
          {byCategoryList.map(c => (
            <div key={c.category} style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 0', fontSize: 12.5 }}>
              <span>{c.category}</span><b style={{ color: 'var(--success)' }}>{c.wins} wins</b>
            </div>
          ))}
          {byCategoryList.length === 0 && <p style={{ fontSize: 12, color: 'var(--text-muted)' }}>No data.</p>}
        </div>
        <div className="card surface-card">
          <div className="section-title">By Account</div>
          {byAccountList.map(a => (
            <div key={a.name} style={{ display: 'flex', justifyContent: 'space-between', padding: '6px 0', fontSize: 12.5 }}>
              <span>{a.name}</span><b>{a.total} series · {a.wins} wins</b>
            </div>
          ))}
          {byAccountList.length === 0 && <p style={{ fontSize: 12, color: 'var(--text-muted)' }}>No data.</p>}
        </div>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════
// SCREEN 8 — VIDEO SPLITTER
// ════════════════════════════════════════════════════════════
function VideoSplitter() {
  const [job, setJob] = useState(null);
  const [inputMode, setInputMode] = useState('upload');
  const [videoUrl, setVideoUrl] = useState('');
  const [uploading, setUploading] = useState(false);
  const [downloadPct, setDownloadPct] = useState(0);
  const [uploadPct, setUploadPct] = useState(0);
  const [mode, setMode] = useState('smart');
  const [segSeconds, setSegSeconds] = useState(120);
  const [timestamps, setTimestamps] = useState('');
  const [parts, setParts] = useState(null);
  const [processing, setProcessing] = useState(false);
  const fileRef = useRef();

  // Smart-split state
  const [minPart, setMinPart] = useState(60);
  const [maxPart, setMaxPart] = useState(180);
  const [model, setModel] = useState('small');
  const [language, setLanguage] = useState('auto');
  const [analyzing, setAnalyzing] = useState(false);
  const [suggested, setSuggested] = useState(null);   // array of cut objects
  const [transcriptLang, setTranscriptLang] = useState('');

  async function upload(file) {
    if (!file) return;
    setUploading(true);
    setUploadPct(0);
    setParts(null);
    setSuggested(null);
    const fd = new FormData();
    fd.append('video', file);
    try {
      const data = await uploadFormData('/video/upload', fd, {
        onProgress: setUploadPct,
      });
      setJob(data);
      toast('Uploaded: ' + data.duration_formatted);
    } catch (e) { toast(e.message); }
    setUploadPct(0);
    setUploading(false);
  }

  async function downloadFromUrl() {
    if (!videoUrl.trim()) return toast('Paste a video URL');
    setUploading(true); setParts(null); setSuggested(null); setDownloadPct(0);
    try {
      const r = await api('/video/download-url', { method: 'POST', body: { url: videoUrl.trim() } });
      const poll = setInterval(async () => {
        const st = await api('/video/jobs/' + r.job_id + '/status');
        setDownloadPct(st.progress_pct || 0);
        if (st.status === 'uploaded') {
          clearInterval(poll);
          const dur = await fetch(API + '/video/jobs', { credentials: 'include' }).then(x => x.json());
          const j = dur.find(x => x.id === r.job_id);
          setJob({ id: r.job_id, filename: st.original_filename, duration_formatted: 'ready' });
          toast('Download complete'); setUploading(false);
        } else if (st.status === 'error') {
          clearInterval(poll);
          toast(st.error || 'Download failed'); setUploading(false);
        }
      }, 2000);
    } catch (e) { toast(e.message); setUploading(false); }
  }

  // Smart: analyze (transcribe + suggest cuts)
  async function analyze() {
    if (!job) return;
    setAnalyzing(true); setSuggested(null); setParts(null);
    try {
      const r = await api('/video/smart-analyze', {
        method: 'POST',
        body: { job_id: job.id, min_part: parseInt(minPart), max_part: parseInt(maxPart), model, language }
      });
      setSuggested(r.cuts);
      setTranscriptLang(r.language);
      toast(`Transcribed (${r.language}) — ${r.cuts.length} cuts suggested`);
    } catch (e) { toast(e.message); }
    setAnalyzing(false);
  }

  // Edit a suggested cut's end time (and chain the next start)
  function editCutEnd(i, newFmt) {
    const secs = fmtToSec(newFmt);
    if (secs == null) return;
    const next = [...suggested];
    next[i] = { ...next[i], end: secs, end_fmt: secToFmt(secs), length_sec: Math.round(secs - next[i].start) };
    if (next[i + 1]) {
      next[i + 1] = { ...next[i + 1], start: secs, start_fmt: secToFmt(secs), length_sec: Math.round(next[i + 1].end - secs) };
    }
    setSuggested(next);
  }
  function removeCut(i) {
    if (suggested.length <= 1) return;
    const next = [...suggested];
    if (next[i + 1]) {
      next[i + 1] = { ...next[i + 1], start: next[i].start, start_fmt: next[i].start_fmt, length_sec: Math.round(next[i + 1].end - next[i].start) };
    } else if (next[i - 1]) {
      next[i - 1] = { ...next[i - 1], end: next[i].end, end_fmt: next[i].end_fmt, length_sec: Math.round(next[i].end - next[i - 1].start) };
    }
    next.splice(i, 1);
    setSuggested(next.map((c, idx) => ({ ...c, part: idx + 1 })));
  }

  async function runSmartSplit() {
    if (!suggested) return;
    setProcessing(true); setParts(null);
    try {
      const cuts = suggested.map(c => ({ start: c.start, end: c.end }));
      const r = await api('/video/smart-split', { method: 'POST', body: { job_id: job.id, cuts } });
      setParts(r.outputs.map(o => ({ ...o, job_id: job.id })));
      toast(r.parts + ' parts created');
    } catch (e) { toast(e.message); }
    setProcessing(false);
  }

  // Manual modes (custom / equal)
  async function split() {
    if (!job) return;
    setProcessing(true); setParts(null);
    try {
      let r;
      if (mode === 'equal') {
        r = await api('/video/split-equal', { method: 'POST', body: { job_id: job.id, segment_seconds: parseInt(segSeconds) } });
      } else {
        const ts = timestamps.split('\n').map(t => t.trim()).filter(Boolean);
        r = await api('/video/split-custom', { method: 'POST', body: { job_id: job.id, timestamps: ts } });
      }
      setParts(r.outputs.map(o => ({ ...o, job_id: job.id }))); toast(r.parts + ' parts created');
    } catch (e) { toast(e.message); }
    setProcessing(false);
  }

  const reasonColor = { 'sentence-end': 'var(--green)', 'pause': 'var(--accent)', 'forced': 'var(--primary)', 'final': 'var(--text-dim)', 'equal-fallback': 'var(--yellow)' };
  const reasonLabel = { 'sentence-end': 'sentence end', 'pause': 'natural pause', 'forced': 'forced — review', 'final': 'final part', 'equal-fallback': 'no transcript' };

  return (
    <div>
      <PageHeader
        eyebrow="Utility"
        title="Video Splitter"
        subtitle="Upload, analyze, and cut long source videos into production-ready parts with less manual work."
      />
      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">Video Input</div>
        <p style={{ fontSize: 12.5, color: 'var(--text-dim)', marginBottom: 14 }}>
          Upload a file or paste a link. yt-dlp supports YouTube, Dailymotion, and many sites.
          <b> Note:</b> Instagram/Snapchat may fail — use Upload as fallback. Lossless split, source never deleted.
        </p>
        <div className="tabs" style={{ marginBottom: 14 }}>
          <button className={'tab' + (inputMode === 'upload' ? ' active' : '')} onClick={() => setInputMode('upload')}>Upload File</button>
          <button className={'tab' + (inputMode === 'link' ? ' active' : '')} onClick={() => setInputMode('link')}>Paste Link</button>
        </div>
        {inputMode === 'upload' ? (
          <div className="dz" onClick={() => !uploading && fileRef.current.click()}>
            <input ref={fileRef} type="file" accept="video/*" style={{ display: 'none' }} onChange={e => upload(e.target.files[0])} />
            {uploading ? (
              <div style={{ width: '100%', maxWidth: 420, margin: '0 auto' }}>
                <UploadProgressBar pct={uploadPct} label={`Uploading ${uploadPct || 0}%`} />
              </div>
            ) : job ? (
              <div>
                <div className="dz-icon"><NavIcon name="Video" size={32} /></div>
                <b>{job.filename}</b>
                <div style={{ color: 'var(--text-muted)', fontSize: 13, marginTop: 4 }}>Duration: {job.duration_formatted} — click to replace</div>
              </div>
            ) : (
              <div>
                <div className="dz-icon"><NavIcon name="Upload" size={32} /></div>
                <b>Click to upload a video</b>
                <div style={{ color: 'var(--text-dim)', fontSize: 12, marginTop: 4 }}>MP4, MOV, etc.</div>
              </div>
            )}
          </div>
        ) : (
          <div>
            <div className="field"><label>Video URL</label><input value={videoUrl} onChange={e => setVideoUrl(e.target.value)} placeholder="https://youtube.com/watch?v=…" /></div>
            {uploading && <div className="progress-bar" style={{ marginBottom: 10 }}><div className="progress-fill" style={{ width: downloadPct + '%' }} /></div>}
            <button className="btn" onClick={downloadFromUrl} disabled={uploading}>{uploading ? `Downloading… ${downloadPct}%` : 'Download Video'}</button>
            {job && inputMode === 'link' && <p style={{ fontSize: 12, color: 'var(--success)', marginTop: 8, display: 'flex', alignItems: 'center', gap: 6 }}><NavIcon name="Check" size={14} /> {job.filename} ready</p>}
          </div>
        )}
      </div>

      {job && (
        <div className="card surface-card" style={{ marginBottom: 18 }}>
          <div className="section-title">Split Method</div>
          <div className="tabs" style={{ marginBottom: 16 }}>
            <button type="button" className={'tab' + (mode === 'smart' ? ' active' : '')} onClick={() => setMode('smart')}><NavIcon name="Sparkles" size={14} /> Smart (AI)</button>
            <button type="button" className={'tab' + (mode === 'equal' ? ' active' : '')} onClick={() => setMode('equal')}>Exact / Equal</button>
            <button type="button" className={'tab' + (mode === 'custom' ? ' active' : '')} onClick={() => setMode('custom')}>Manual Timestamps</button>
          </div>

          {mode === 'smart' && (
            <div>
              <div className="field-row">
                <div className="field"><label>Min part length (seconds)</label><input type="number" value={minPart} onChange={e => setMinPart(e.target.value)} /></div>
                <div className="field"><label>Max part length (seconds)</label><input type="number" value={maxPart} onChange={e => setMaxPart(e.target.value)} /></div>
              </div>
              <div className="field-row">
                <div className="field"><label>Whisper model</label>
                  <select value={model} onChange={e => setModel(e.target.value)}>
                    <option value="tiny">tiny — fastest, least accurate</option>
                    <option value="base">base</option>
                    <option value="small">small — recommended</option>
                    <option value="medium">medium — slowest, most accurate</option>
                  </select>
                </div>
                <div className="field"><label>Language</label>
                  <select value={language} onChange={e => setLanguage(e.target.value)}>
                    <option value="auto">Auto-detect</option>
                    <option value="ar">Arabic</option>
                    <option value="en">English</option>
                  </select>
                </div>
              </div>
              <button type="button" className="btn" onClick={analyze} disabled={analyzing}>
                {analyzing ? 'Transcribing… (this can take a few minutes)' : <><NavIcon name="Sparkles" size={16} /> Transcribe &amp; Suggest Cuts</>}
              </button>
              {analyzing && <div style={{ marginTop: 10 }}><Spinner /><p style={{ fontSize: 11.5, color: 'var(--text-muted)', textAlign: 'center' }}>Whisper is reading the whole video. Longer videos take longer.</p></div>}

              {suggested && (
                <div style={{ marginTop: 20 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
                    <div className="section-title" style={{ margin: 0 }}>{suggested.length} Suggested Cuts <span style={{ fontSize: 11, color: 'var(--text-muted)', fontWeight: 400 }}>({transcriptLang})</span></div>
                  </div>
                  <p style={{ fontSize: 11.5, color: 'var(--text-muted)', marginBottom: 12 }}>Review the cut points. Each shows the sentence it ends on. Edit the end time or remove a cut if needed. &quot;forced&quot; cuts had no clean sentence in the window — check those.</p>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
                    {suggested.map((c, i) => (
                      <div key={i} className="cut-card" style={{ borderRightColor: reasonColor[c.reason] || 'var(--border)' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
                          <b style={{ fontSize: 13 }}>Part {String(c.part).padStart(2, '0')}</b>
                          <span style={{ fontSize: 12, color: 'var(--text-dim)' }}>{secToFmt(c.start)} →</span>
                          <input value={c.end_fmt} onChange={e => editCutEnd(i, e.target.value)}
                            style={{ width: 90, padding: '4px 8px', height: 'auto', fontSize: 12, fontFamily: 'monospace' }}
                            disabled={i === suggested.length - 1} />
                          <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>{c.length_sec}s</span>
                          <span style={{ fontSize: 10.5, color: reasonColor[c.reason], marginLeft: 'auto' }}>{reasonLabel[c.reason] || c.reason}</span>
                          {suggested.length > 1 && i !== suggested.length - 1 && <button type="button" className="btn-ghost btn btn-sm" onClick={() => removeCut(i)} aria-label="Remove cut"><NavIcon name="X" size={14} /></button>}
                        </div>
                        {c.end_text && <div style={{ fontSize: 11.5, color: 'var(--text-dim)', marginTop: 6, direction: 'rtl', textAlign: 'right', fontStyle: 'italic' }}>…{c.end_text}</div>}
                      </div>
                    ))}
                  </div>
                  <button type="button" className="btn" style={{ marginTop: 16 }} onClick={runSmartSplit} disabled={processing}>
                    {processing ? 'Splitting…' : <><NavIcon name="Scissors" size={16} /> Split Into These Parts</>}
                  </button>
                </div>
              )}
            </div>
          )}

          {mode === 'equal' && (
            <div className="field">
              <label>Seconds per part</label>
              <input type="number" value={segSeconds} onChange={e => setSegSeconds(e.target.value)} />
              <p style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 6 }}>Note: lossless cuts land on the nearest keyframe, so equal parts are approximate. For exact control use Smart Split or Custom Timestamps.</p>
              <button type="button" className="btn" style={{ marginTop: 12 }} onClick={split} disabled={processing}>
                {processing ? 'Splitting…' : <><NavIcon name="Scissors" size={16} /> Split Video</>}
              </button>
            </div>
          )}

          {mode === 'custom' && (
            <div className="field">
              <label>Cut points (one timestamp per line, HH:MM:SS)</label>
              <textarea value={timestamps} onChange={e => setTimestamps(e.target.value)} placeholder={"00:10:30\n00:21:45\n00:33:10"} style={{ minHeight: 120, fontFamily: 'monospace' }} />
              <p style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 6 }}>Each timestamp is where a new part begins. 2 timestamps = 3 parts.</p>
              <button type="button" className="btn" style={{ marginTop: 12 }} onClick={split} disabled={processing}>
                {processing ? 'Splitting…' : <><NavIcon name="Scissors" size={16} /> Split Video</>}
              </button>
            </div>
          )}
        </div>
      )}

      {parts && (
        <div className="card surface-card">
          <SectionTitle icon={<NavIcon name="Check" size={14} />} style={{ color: 'var(--success)' }}>{parts.length} Parts Created</SectionTitle>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {parts.map(p => (
              <div key={p.part} className="perf-row">
                <div style={{ flex: 1 }}>
                  <b>{p.filename}</b>
                  <span style={{ color: 'var(--text-dim)', fontSize: 12, marginLeft: 10 }}>{p.start} → {p.end}</span>
                </div>
                <a className="btn btn-sm" href={'/api/video/download/' + p.job_id + '/' + p.filename} download><NavIcon name="Download" size={14} /> Download</a>
              </div>
            ))}
          </div>
          <p style={{ fontSize: 11.5, color: 'var(--text-muted)', marginTop: 12 }}>Files saved on the server under outputs/{parts[0]?.job_id}/. Remember: original Part 1 may be a slow intro — consider moving a gripping later moment to the front of Episode 1 as the hook.</p>
        </div>
      )}
    </div>
  );
}

// time helpers for the splitter
function secToFmt(sec) {
  const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = Math.floor(sec % 60);
  return [h, m, s].map(v => String(v).padStart(2, '0')).join(':');
}
function fmtToSec(fmt) {
  const p = String(fmt).split(':').map(Number);
  if (p.some(isNaN)) return null;
  if (p.length === 3) return p[0] * 3600 + p[1] * 60 + p[2];
  if (p.length === 2) return p[0] * 60 + p[1];
  return p[0];
}

// ════════════════════════════════════════════════════════════
// SCREEN 9 — SETTINGS
// ════════════════════════════════════════════════════════════
function Settings({ onSaved }) {
  const [s, setS] = useState(null);
  const [cats, setCats] = useState([]);
  const [team, setTeam] = useState([]);
  const [saving, setSaving] = useState(false);
  const [newCat, setNewCat] = useState('');

  const load = useCallback(async () => {
    const data = await api('/settings');
    setS(data);
    try { setCats(await api('/categories')); } catch { setCats(JSON.parse(data.categories || '[]')); }
    setTeam(await api('/team'));
  }, []);
  useEffect(() => { load(); }, [load]);

  if (!s) return <Spinner />;

  async function save() {
    setSaving(true);
    try {
      await api('/settings', { method: 'PUT', body: s });
      toast('Settings saved'); onSaved && onSaved();
    } catch (e) { toast(e.message); }
    setSaving(false);
  }

  async function addCategory() {
    if (!newCat.trim()) return;
    const c = await api('/categories', { method: 'POST', body: { name: newCat.trim() } });
    setCats([...cats, c]); setNewCat('');
  }
  async function updateCat(id, field, val) {
    const c = await api('/categories/' + id, { method: 'PUT', body: { [field]: val } });
    setCats(cats.map(x => x.id === id ? c : x));
  }
  async function delCat(id) {
    await api('/categories/' + id, { method: 'DELETE' });
    setCats(cats.filter(c => c.id !== id));
  }

  async function addTeam() {
    const t = await api('/team', { method: 'POST', body: { name: 'New Member', role: 'editor' } });
    setTeam([...team, t]);
  }
  async function updateTeam(id, field, val) {
    await api('/team/' + id, { method: 'PUT', body: { [field]: val } });
    setTeam(team.map(t => t.id === id ? { ...t, [field]: val } : t));
  }
  async function delTeam(id) { await api('/team/' + id, { method: 'DELETE' }); setTeam(team.filter(t => t.id !== id)); }

  return (
    <div>
      <PageHeader
        eyebrow="Administration"
        title="Settings"
        subtitle="Control targets, workflow thresholds, AI integrations, categories, and operational defaults."
        meta={
          <>
            <ContextPill label="Categories" value={cats.length} />
            <ContextPill label="Team" value={team.length} />
          </>
        }
        actions={can('settings', 'edit') ? <button className="btn" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save All Settings'}</button> : null}
      />
      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">General</div>
        <div className="field-row">
          <div className="field"><label>Daily Series Target</label><input type="number" value={s.daily_target} onChange={e => setS({ ...s, daily_target: e.target.value })} /></div>
          <div className="field"><label>Performance Threshold (Ep1 views = winner)</label><input type="number" value={s.performance_threshold} onChange={e => setS({ ...s, performance_threshold: e.target.value })} /></div>
        </div>
        <div className="field-row">
          <div className="field"><label>Bottleneck Threshold (cards/stage)</label><input type="number" value={s.bottleneck_threshold || 8} onChange={e => setS({ ...s, bottleneck_threshold: e.target.value })} /></div>
          <div className="field"><label>Stuck Warning (days in stage)</label><input type="number" value={s.stage_stuck_warning_days || 3} onChange={e => setS({ ...s, stage_stuck_warning_days: e.target.value })} /></div>
        </div>
        <div className="field-row">
          <div className="field"><label>Track A %</label><input type="number" value={s.track_a_target} onChange={e => setS({ ...s, track_a_target: e.target.value })} /></div>
          <div className="field"><label>Track B %</label><input type="number" value={s.track_b_target} onChange={e => setS({ ...s, track_b_target: e.target.value })} /></div>
          <div className="field"><label>Track C %</label><input type="number" value={s.track_c_target} onChange={e => setS({ ...s, track_c_target: e.target.value })} /></div>
          <div className="field"><label>Track D %</label><input type="number" value={s.track_d_target} onChange={e => setS({ ...s, track_d_target: e.target.value })} /></div>
        </div>
      </div>

      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">Anthropic API Key (AI Studio)</div>
        <input type="password" value={s.anthropic_api_key || ''} onChange={e => setS({ ...s, anthropic_api_key: e.target.value })} placeholder="sk-ant-…" />
      </div>

      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">OpenRouter (AI Assistant + Research)</div>
        <div className="field-row">
          <div className="field"><label>API Key</label><input type="password" value={s.openrouter_api_key || ''} onChange={e => setS({ ...s, openrouter_api_key: e.target.value })} placeholder="or set OPENROUTER_API_KEY in .env" /></div>
          <div className="field"><label>Model ID</label><input value={s.openrouter_model || 'meta-llama/llama-3.1-8b-instruct:free'} onChange={e => setS({ ...s, openrouter_model: e.target.value })} /></div>
        </div>
      </div>

      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">Categories ({cats.length})</div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
          <input value={newCat} onChange={e => setNewCat(e.target.value)} placeholder="New category name…" style={{ flex: 1 }} />
          <button className="btn btn-sm" onClick={addCategory}>+ Add</button>
        </div>
        <div style={{ display: 'grid', gap: 10 }} className="grid-3">
          {cats.map(c => (
            <div key={c.id || c.name} style={{ display: 'flex', alignItems: 'center', gap: 8, background: 'var(--bg)', padding: '8px 10px', borderRadius: 8 }}>
              <input type="color" value={c.color || '#E63946'} onChange={e => c.id && updateCat(c.id, 'color', e.target.value)} style={{ width: 28, height: 28, padding: 0, border: 'none' }} />
              <span style={{ flex: 1, fontSize: 12 }}>{c.name}</span>
              <input type="number" value={c.daily_target || 2} onChange={e => c.id && updateCat(c.id, 'daily_target', parseInt(e.target.value) || 0)} style={{ width: 50, padding: '4px 6px', height: 'auto', fontSize: 12 }} />
              {c.id && <button type="button" className="btn-ghost btn btn-sm" onClick={() => delCat(c.id)} aria-label="Delete category"><NavIcon name="X" size={14} /></button>}
            </div>
          ))}
        </div>
      </div>

      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span>Team Members</span>
          <button className="btn btn-sm" onClick={addTeam}>+ Add</button>
        </div>
        {team.map(t => (
          <div key={t.id} style={{ display: 'flex', gap: 10, alignItems: 'center', padding: '8px 0', borderBottom: '1px solid var(--border)' }}>
            <input value={t.name} onChange={e => updateTeam(t.id, 'name', e.target.value)} style={{ flex: 1 }} />
            <select value={t.role} onChange={e => updateTeam(t.id, 'role', e.target.value)} style={{ width: 160 }}>
              <option value="ops_manager">Operations Manager</option>
              <option value="editor">Editor</option>
              <option value="qc">QC Reviewer</option>
              <option value="poster">Poster</option>
            </select>
            <button type="button" className="btn-ghost btn btn-sm" onClick={() => delTeam(t.id)} aria-label="Remove member"><NavIcon name="X" size={14} /></button>
          </div>
        ))}
      </div>

    </div>
  );
}

// ─── Mount ──────────────────────────────────────────────────

// REPORTS
function Reports({ settings }) {
  const today = new Date().toISOString().split('T')[0];
  const [from, setFrom] = useState(today);
  const [to, setTo] = useState(today);
  const [data, setData] = useState(null);

  const load = useCallback(async () => {
    setData(await api('/reports?from=' + from + '&to=' + to));
  }, [from, to]);
  useEffect(() => { load(); }, [load]);

  if (!data) return <Spinner />;

  return (
    <div>
      <PageHeader
        eyebrow="Reporting"
        title="Reports"
        subtitle="Measure output over time and break it down by account, category, and stage health."
      />
      {data.belowTarget && (
        <AlertBanner>
          Below target: <b>{data.published}</b> published vs <b>{data.periodTarget}</b> objective — <b>{data.deficit}</b> short
        </AlertBanner>
      )}
      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div style={{ display: 'flex', gap: 12, alignItems: 'flex-end', flexWrap: 'wrap' }}>
          <div className="field" style={{ marginBottom: 0 }}><label>From</label><input type="date" value={from} onChange={e => setFrom(e.target.value)} /></div>
          <div className="field" style={{ marginBottom: 0 }}><label>To</label><input type="date" value={to} onChange={e => setTo(e.target.value)} /></div>
          <button className="btn" onClick={load}>Generate</button>
          {can('reports', 'export') && <button className="btn-ghost btn" onClick={() => exportExcel('/reports/export?from=' + from + '&to=' + to, 'redz-report.xlsx')}>Export Excel</button>}
        </div>
      </div>
      <div className="grid grid-4 mb20">
        <div className="stat-card"><div className="stat-label">Produced</div><div className="stat-value">{data.totalProduced}</div></div>
        <div className="stat-card"><div className="stat-label">Published</div><div className="stat-value" style={{ color: 'var(--success)' }}>{data.published}</div></div>
        <div className="stat-card"><div className="stat-label">Target</div><div className="stat-value">{data.periodTarget}</div></div>
        <div className="stat-card"><div className="stat-label">Deficit</div><div className="stat-value" style={{ color: data.deficit ? 'var(--primary)' : 'var(--success)' }}>{data.deficit}</div></div>
      </div>
      <div className="grid grid-2 mb20">
        <div className="card surface-card">
          <div className="section-title">By Account</div>
          <div className="table-wrap">
            <table className="tbl"><thead><tr><th>Account</th><th>Count</th><th>Published</th></tr></thead>
              <tbody>{data.byAccount.map(a => <tr key={a.username}><td>{a.name}</td><td>{a.count}</td><td>{a.published}</td></tr>)}</tbody></table>
          </div>
        </div>
        <div className="card surface-card">
          <div className="section-title">By Category</div>
          <div className="table-wrap">
            <table className="tbl"><thead><tr><th>Category</th><th>Count</th><th>Published</th></tr></thead>
              <tbody>{data.byCategory.map(c => <tr key={c.category}><td>{c.category}</td><td>{c.count}</td><td>{c.published}</td></tr>)}</tbody></table>
          </div>
        </div>
      </div>
      <div className="card surface-card" style={{ marginBottom: 18 }}>
        <div className="section-title">Pipeline Stage Counts</div>
        <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          {STAGES.filter(st => st.id !== 'published').map(st => (
            <div key={st.id} className="stage-bar-item">
              <span>{st.label}</span>
              <div className="stage-bar"><div className="stage-bar-fill" style={{ width: Math.min(100, (data.byStage[st.id] || 0) * 8) + '%' }} /></div>
              <b>{data.byStage[st.id] || 0}</b>
            </div>
          ))}
        </div>
      </div>
      <div className="card surface-card">
        <SectionTitle icon={<NavIcon name="Trophy" size={14} />} style={{ color: 'var(--success)' }}>Winners</SectionTitle>
        {data.winners.length === 0 && <p style={{ color: 'var(--text-muted)', fontSize: 13 }}>No winners in this period.</p>}
        {data.winners.map((w, i) => (
          <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0', borderBottom: '1px solid var(--border)', fontSize: 13 }}>
            <span>{w.title}</span><span style={{ color: 'var(--success)', fontWeight: 700 }}>{Number(w.ep1_views).toLocaleString()} views</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// USERS
const SCREEN_ACTIONS_UI = {
  dashboard: ['view'], pipeline: ['view','create','edit','delete','move_stage','export'],
  accounts: ['view','create','edit','delete','export'], research: ['view','create','edit','delete','approve','export'],
  aistudio: ['view','use'], qc: ['view','edit','approve'], performance: ['view','create','export'],
  reports: ['view','export'], video: ['view','use'], users: ['view','create','edit','delete'], settings: ['view','edit'],
};
const SCREENS_UI = Object.keys(SCREEN_ACTIONS_UI);

function UsersScreen() {
  const [users, setUsers] = useState([]);
  const [editing, setEditing] = useState(null);

  const load = useCallback(async () => setUsers(await api('/users')), []);
  useEffect(() => { load(); }, [load]);

  async function saveUser(u) {
    if (u.id) await api('/users/' + u.id, { method: 'PUT', body: u });
    else await api('/users', { method: 'POST', body: u });
    setEditing(null); load();
  }

  async function delUser(id) {
    if (!confirm('Delete user?')) return;
    await api('/users/' + id, { method: 'DELETE' }); load();
  }

  async function loadPreset(role, user) {
    const preset = await api('/users/presets/' + role);
    setEditing({ ...user, role, permissions: preset });
  }

  return (
    <div>
      <PageHeader
        eyebrow="Administration"
        title="Users"
        subtitle="Manage team access, keep permissions organized, and make onboarding less manual."
        meta={<ContextPill label="Users" value={users.length} />}
        actions={can('users', 'create') ? <button type="button" className="btn" onClick={() => setEditing({ name: '', email: '', password: '', role: 'viewer', permissions: null })}><NavIcon name="Plus" size={16} /> Add User</button> : null}
      />
      {users.length === 0 ? (
        <div className="card surface-card">
          <EmptyState
            icon={<NavIcon name="Users" size={40} />}
            title="No team members yet"
            message="Create the first user account so each role can get the right access to pipeline, QC, and reporting."
            action={can('users', 'create') ? <button type="button" className="btn" onClick={() => setEditing({ name: '', email: '', password: '', role: 'viewer', permissions: null })}><NavIcon name="Plus" size={16} /> Add first user</button> : null}
          />
        </div>
      ) : (
        <div className="card table-wrap">
          <table className="tbl"><thead><tr><th>Name</th><th>Email</th><th>Role</th><th>Status</th><th></th></tr></thead>
            <tbody>{users.map(u => (
              <tr key={u.id}><td>{u.name}</td><td>{u.email}</td><td>{u.role}</td><td>{u.status}</td>
                <td>{can('users', 'edit') && <button className="btn-ghost btn btn-sm" onClick={() => setEditing({ ...u, password: '' })}>Edit</button>}</td>
              </tr>
            ))}</tbody>
          </table>
        </div>
      )}
      {editing && (
        <div className="modal-overlay" onClick={() => setEditing(null)}>
          <div className="modal modal-lg" onClick={e => e.stopPropagation()}>
            <div className="modal-head"><h2>{editing.id ? 'Edit User' : 'New User'}</h2><CloseButton onClick={() => setEditing(null)} /></div>
            <div className="modal-body">
              <div className="field-row">
                <div className="field"><label>Name</label><input value={editing.name} onChange={e => setEditing({ ...editing, name: e.target.value })} /></div>
                <div className="field"><label>Email</label><input value={editing.email} onChange={e => setEditing({ ...editing, email: e.target.value })} /></div>
              </div>
              <div className="field-row">
                <div className="field"><label>Password{editing.id ? ' (leave blank to keep)' : ''}</label><input type="password" value={editing.password || ''} onChange={e => setEditing({ ...editing, password: e.target.value })} /></div>
                <div className="field"><label>Role Preset</label>
                  <select value={editing.role} onChange={e => loadPreset(e.target.value, editing)}>
                    {['admin','manager','editor','sourcer','viewer'].map(r => <option key={r} value={r}>{r}</option>)}
                  </select>
                </div>
              </div>
              {editing.permissions && (
                <div className="perm-grid">
                  <div className="perm-header"><span>Screen</span><span>Access</span>{['view','create','edit','delete','move_stage','approve','export','use'].map(a => <span key={a}>{a}</span>)}</div>
                  {SCREENS_UI.map(screen => (
                    <div key={screen} className="perm-row">
                      <span>{screen}</span>
                      <input type="checkbox" checked={!!editing.permissions.screens?.[screen]} onChange={e => {
                        const p = JSON.parse(JSON.stringify(editing.permissions));
                        p.screens[screen] = e.target.checked;
                        setEditing({ ...editing, permissions: p });
                      }} />
                      {['view','create','edit','delete','move_stage','approve','export','use'].map(action => (
                        <input key={action} type="checkbox" disabled={!SCREEN_ACTIONS_UI[screen]?.includes(action)}
                          checked={!!editing.permissions.actions?.[screen]?.[action]}
                          onChange={e => {
                            const p = JSON.parse(JSON.stringify(editing.permissions));
                            if (!p.actions[screen]) p.actions[screen] = {};
                            p.actions[screen][action] = e.target.checked;
                            setEditing({ ...editing, permissions: p });
                          }} />
                      ))}
                    </div>
                  ))}
                </div>
              )}
            </div>
            <div className="modal-foot">
              {editing.id && can('users', 'delete') && <button className="btn-danger btn" onClick={() => delUser(editing.id)}>Delete</button>}
              <button className="btn-ghost btn" onClick={() => setEditing(null)}>Cancel</button>
              <button className="btn" onClick={() => saveUser(editing)}>Save</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// AI ASSISTANT WIDGET
function AssistantWidget() {
  const [open, setOpen] = useState(false);
  const [msgs, setMsgs] = useState([{ role: 'assistant', content: 'Hi! I can help with titles, ideas, pipeline actions, and research. What do you need?' }]);
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);
  const [researchMode, setResearchMode] = useState(false);

  async function send() {
    if (!input.trim() || loading) return;
    const userMsg = { role: 'user', content: input.trim() };
    const newMsgs = [...msgs, userMsg];
    setMsgs(newMsgs); setInput(''); setLoading(true);
    try {
      const r = await api('/assistant/chat', { method: 'POST', body: { messages: newMsgs.filter(m => m.role !== 'system'), research_mode: researchMode } });
      const actionsNote = r.actions_taken?.length ? '\n\nActions: ' + r.actions_taken.map(a => a.type).join(', ') : '';
      setMsgs([...newMsgs, { role: 'assistant', content: r.reply + actionsNote }]);
    } catch (e) { setMsgs([...newMsgs, { role: 'assistant', content: 'Error: ' + e.message }]); }
    setLoading(false);
  }

  return (
  <>
    {open && <div className="assistant-overlay" onClick={() => setOpen(false)} />}
    <button type="button" className="assistant-fab" onClick={() => setOpen(!open)} aria-label="Open AI assistant">
      <NavIcon name="MessageCircle" size={24} />
    </button>
    {open && (
      <div className="assistant-panel" role="dialog" aria-label="REDZ Assistant">
        <div className="assistant-head">
          <NavIcon name="MessageCircle" size={18} />
          <b>REDZ Assistant</b>
          <label className="research-toggle">
            <input type="checkbox" checked={researchMode} onChange={e => setResearchMode(e.target.checked)} />
            Research mode
          </label>
          <CloseButton onClick={() => setOpen(false)} />
        </div>
        <div className="assistant-msgs">
          {msgs.map((m, i) => <div key={i} className={'assistant-msg ' + m.role}>{m.content}</div>)}
          {loading && <div className="assistant-msg assistant">Thinking…</div>}
        </div>
        <div className="assistant-input">
          <input value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => e.key === 'Enter' && send()} placeholder="Ask or command…" aria-label="Message" />
          <button type="button" className="btn btn-sm" onClick={send} disabled={loading}>Send</button>
        </div>
      </div>
    )}
  </>
  );
}

// IMPORT MODAL
function ImportModal({ type, onClose, onDone }) {
  const [preview, setPreview] = useState(null);
  const [importing, setImporting] = useState(false);
  const [uploading, setUploading] = useState(false);
  const [uploadPct, setUploadPct] = useState(0);
  const [commitErrors, setCommitErrors] = useState([]);
  const typeLabel = type === 'ideas' ? 'ideas sheet' : type;
  const isIdeas = type === 'ideas';
  const validCount = isIdeas && preview
    ? (preview.validCount ?? preview.rows.filter(r => r.row_valid).length)
    : preview?.total || 0;
  const invalidCount = preview ? Math.max(0, (preview.total || preview.rows.length) - validCount) : 0;

  async function handleFile(e) {
    const file = e.target.files[0];
    if (!file) return;
    setUploading(true);
    setUploadPct(0);
    setPreview(null);
    setCommitErrors([]);
    const fd = new FormData();
    fd.append('file', file);
    fd.append('type', type);
    try {
      const data = await uploadFormData('/export/import/preview', fd, {
        onProgress: setUploadPct,
      });
      setPreview(data);
    } catch (err) {
      toast(err.message);
    } finally {
      setUploadPct(0);
      setUploading(false);
    }
  }

  async function commit() {
    if (isIdeas && validCount === 0) return;
    setImporting(true);
    setCommitErrors([]);
    try {
      const r = await api('/export/import/commit', { method: 'POST', body: { type, rows: preview.rows } });
      if (isIdeas) {
        toast(`Added ${r.created} idea${r.created !== 1 ? 's' : ''} to Pipeline (Idea stage)`);
      } else {
        toast('Imported ' + r.created + ' rows');
      }
      onDone();
    } catch (e) {
      toast(e.message);
      if (e.errors?.length) setCommitErrors(e.errors);
      setImporting(false);
    }
  }

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal modal-lg" onClick={e => e.stopPropagation()}>
        <div className="modal-head"><h2>Import {typeLabel}</h2><CloseButton onClick={onClose} /></div>
        <div className="modal-body">
          {isIdeas && (
            <div style={{ fontSize: 12.5, color: 'var(--text-dim)', marginBottom: 12 }}>
              Expected columns: <b>title</b>, <b>link</b>, <b>account</b>. Imported rows will create pipeline cards directly at the <b>idea</b> stage.
            </div>
          )}
          <input type="file" accept=".xlsx,.xls,.csv" onChange={handleFile} disabled={uploading} />
          {uploading && <UploadProgressBar pct={uploadPct} label={`Uploading ${uploadPct || 0}%`} />}
          {preview && (
            <div style={{ marginTop: 16 }}>
              <p style={{ fontSize: 13, marginBottom: 8 }}>
                Preview: {preview.total} rows detected. Mapping: {JSON.stringify(preview.mapping)}
                {isIdeas && (
                  <span style={{ marginLeft: 8, color: validCount ? 'var(--success, #2ecc71)' : 'var(--danger, #e63946)' }}>
                    {validCount} valid / {invalidCount} invalid
                  </span>
                )}
              </p>
              {isIdeas && invalidCount > 0 && (
                <p style={{ fontSize: 12, color: 'var(--danger, #e63946)', marginBottom: 8 }}>
                  Fix invalid rows before importing. Account must match an existing account name or username (case-insensitive).
                </p>
              )}
              <div style={{ maxHeight: 300, overflow: 'auto', fontSize: 12 }}>
                {preview.rows.slice(0, 10).map((r, i) => (
                  <div key={i} style={{
                    padding: '6px 0',
                    borderBottom: '1px solid var(--border)',
                    color: isIdeas && r.row_valid === false ? 'var(--danger, #e63946)' : undefined,
                  }}>
                    <b>{r.title || r.account || '—'}</b>
                    {isIdeas && r.row_valid && r.resolved_category && (
                      <span style={{ marginLeft: 8, color: 'var(--text-muted)' }}>→ {r.resolved_category}</span>
                    )}
                    {isIdeas && r.row_valid === false && r.row_error && (
                      <span style={{ marginLeft: 8, fontSize: 11 }}>{r.row_error}</span>
                    )}
                    {type === 'series' && r.resolved_category && (
                      <span style={{ marginLeft: 8, color: 'var(--text-muted)' }}>→ {r.resolved_category}</span>
                    )}
                    {r.account && <span style={{ marginLeft: 8, fontSize: 11, color: 'var(--text-dim)' }}>@{r.resolved_account || r.account}</span>}
                  </div>
                ))}
              </div>
              {commitErrors.length > 0 && (
                <div style={{ marginTop: 12, fontSize: 12, color: 'var(--danger, #e63946)' }}>
                  {commitErrors.slice(0, 5).map((err, i) => (
                    <div key={i}>Row {err.row || '?'}: {err.reason}</div>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>
        <div className="modal-foot">
          <button className="btn-ghost btn" onClick={onClose}>Cancel</button>
          {preview && (
            <button
              className="btn"
              onClick={commit}
              disabled={importing || (isIdeas && validCount === 0)}
            >
              {importing
                ? 'Importing…'
                : isIdeas
                  ? `Import ${validCount} valid row${validCount !== 1 ? 's' : ''}`
                  : 'Import ' + preview.total + ' rows'}
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
