// screens-playout.jsx — Master Control (MCR) playout page.
//
// Operator workflow (Phase A — playlist player):
//   1. Create / pick a channel (output target: SRT / RTMP / NDI / DeckLink).
//   2. Start the channel → spawns the CasparCG sidecar, brings up the output.
//   3. Drag assets from the media bin into the playlist; reorder by dragging.
//      Each item stages from S3 to the CasparCG /media volume in the background.
//   4. Hit PLAY → the engine walks the playlist gaplessly. PAUSE / SKIP / STOP
//      transport. As-run log records what aired.
//
// Talks to /api/v1/playout via window.ZAMPP_API.fetch. Native HTML5 drag-drop,
// no extra library. Components are plain globals (esbuild bundle:false).

const PO_OUTPUTS = [
  { value: 'srt',      label: 'SRT' },
  { value: 'rtmp',     label: 'RTMP' },
  { value: 'ndi',      label: 'NDI' },
  { value: 'decklink', label: 'SDI (DeckLink)' },
];

const PO_FORMATS = ['1080p5994', '1080i5994', '1080p2997', '720p5994', '1080i50', '1080p25'];

async function poFetch(path, opts) {
  return window.ZAMPP_API.fetch('/playout' + path, opts);
}

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

function fmtDuration(secs) {
  if (!secs || secs < 0) return '—';
  const s = Math.floor(secs);
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const ss = s % 60;
  const mm = String(m).padStart(2, '0');
  const ssStr = String(ss).padStart(2, '0');
  return h > 0 ? `${h}:${mm}:${ssStr}` : `${m}:${ssStr}`;
}

function itemEffectiveDuration(it) {
  const total = (it.asset_duration_ms || 0) / 1000;
  const inPt  = it.in_point  != null ? Number(it.in_point)  : 0;
  const outPt = it.out_point != null ? Number(it.out_point) : total;
  return Math.max(0, outPt - inPt);
}

// ── Output-config sub-form (varies by output type) ───────────────────────────
function OutputConfigFields({ type, config, onChange }) {
  const set = (k, v) => onChange({ ...config, [k]: v });
  if (type === 'decklink') {
    return (
      <div className="field">
        <label className="field-label">DeckLink device index</label>
        <input className="field-input" type="number" min="1" value={config.device_index || 1}
          onChange={e => set('device_index', parseInt(e.target.value, 10) || 1)} />
      </div>
    );
  }
  if (type === 'ndi') {
    return (
      <div className="field">
        <label className="field-label">NDI source name</label>
        <input className="field-input" value={config.ndi_name || ''} placeholder="DRAGONFLIGHT CH1"
          onChange={e => set('ndi_name', e.target.value)} />
      </div>
    );
  }
  // srt / rtmp
  return (
    <React.Fragment>
      <div className="field">
        <label className="field-label">{type.toUpperCase()} URL</label>
        <input className="field-input mono" value={config.url || ''}
          placeholder={type === 'srt' ? 'srt://host:9000' : 'rtmp://host/live'}
          onChange={e => set('url', e.target.value)} />
      </div>
      {type === 'rtmp' && (
        <div className="field">
          <label className="field-label">Stream key</label>
          <input className="field-input mono" value={config.key || ''}
            onChange={e => set('key', e.target.value)} />
        </div>
      )}
      {type === 'srt' && (
        <div className="field">
          <label className="field-label">Latency (ms)</label>
          <input className="field-input" type="number" value={config.latency || 200}
            onChange={e => set('latency', parseInt(e.target.value, 10) || 200)} />
        </div>
      )}
    </React.Fragment>
  );
}

// ── Channel create modal ─────────────────────────────────────────────────────
function ChannelCreate({ onClose, onCreated }) {
  const PROJECTS = window.ZAMPP_DATA?.PROJECTS || [];
  const [name, setName] = React.useState('');
  const [outputType, setOutputType] = React.useState('srt');
  const [config, setConfig] = React.useState({});
  const [videoFormat, setVideoFormat] = React.useState('1080i5994');
  const [projectId, setProjectId] = React.useState(PROJECTS[0]?.id || '');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const submit = async () => {
    setBusy(true); setErr(null);
    try {
      const ch = await poFetch('/channels', {
        method: 'POST',
        body: JSON.stringify({
          name, output_type: outputType, output_config: config,
          video_format: videoFormat, project_id: projectId || null,
        }),
      });
      onCreated(ch);
    } catch (e) { setErr(e.message || 'Failed to create channel'); }
    finally { setBusy(false); }
  };

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()} style={{ maxWidth: 460 }}>
        <div className="modal-header"><h3>New Playout Channel</h3></div>
        <div className="modal-body">
          <div className="field">
            <label className="field-label">Name</label>
            <input className="field-input" value={name} autoFocus
              onChange={e => setName(e.target.value)} placeholder="Channel 1" />
          </div>
          <div className="field">
            <label className="field-label">Output</label>
            <select className="field-input" value={outputType}
              onChange={e => { setOutputType(e.target.value); setConfig({}); }}>
              {PO_OUTPUTS.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
            </select>
          </div>
          <OutputConfigFields type={outputType} config={config} onChange={setConfig} />
          <div className="field">
            <label className="field-label">Video format</label>
            <select className="field-input" value={videoFormat} onChange={e => setVideoFormat(e.target.value)}>
              {PO_FORMATS.map(f => <option key={f} value={f}>{f}</option>)}
            </select>
          </div>
          <div className="field">
            <label className="field-label">Project (RBAC scope)</label>
            <select className="field-input" value={projectId} onChange={e => setProjectId(e.target.value)}>
              <option value="">— admin only —</option>
              {PROJECTS.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
            </select>
          </div>
          {err && <div className="alert error">{err}</div>}
        </div>
        <div className="modal-footer">
          <button className="btn ghost" onClick={onClose}>Cancel</button>
          <button className="btn primary" disabled={busy || !name} onClick={submit}>
            {busy ? 'Creating…' : 'Create'}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Media bin: assets draggable into the playlist ────────────────────────────
function MediaBin({ projectId }) {
  const ASSETS = (window.ZAMPP_DATA?.ASSETS || []).filter(a =>
    !projectId || a.project_id === projectId);
  const [q, setQ] = React.useState('');
  const filtered = ASSETS.filter(a => !q || (a.name || '').toLowerCase().includes(q.toLowerCase()));

  const onDragStart = (e, asset) => {
    e.dataTransfer.setData('application/x-df-asset', JSON.stringify({ id: asset.id, name: asset.name }));
    e.dataTransfer.effectAllowed = 'copy';
  };

  return (
    <div className="panel po-bin">
      <div className="po-bin-head">
        <span className="po-section-label">Media Bin</span>
        <input className="field-input sm" placeholder="Filter…" value={q}
          onChange={e => setQ(e.target.value)} style={{ maxWidth: 160 }} />
      </div>
      <div className="po-bin-list">
        {filtered.length === 0 && <div className="muted" style={{ padding: 12 }}>No assets.</div>}
        {filtered.map(a => (
          <div key={a.id} className="po-bin-item" draggable
            onDragStart={e => onDragStart(e, a)} title="Drag into the playlist">
            <span className="po-bin-name">{a.name}</span>
            <span className="mono muted" style={{ fontSize: 11 }}>{a.duration || ''}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── Staging progress bar ──────────────────────────────────────────────────────
function StagingBar({ status }) {
  return (
    <div className={'po-staging-bar po-staging-bar--' + (status || 'pending')} aria-hidden="true" />
  );
}

// ── Playlist: ordered, drag-drop reorder, drop-target for bin assets ─────────
function Playlist({ channel, playlistId, items, activeIndex, onReload }) {
  const [dragIndex, setDragIndex] = React.useState(null);
  const [dropErr, setDropErr] = React.useState(null);

  const onItemDragStart = (e, index) => {
    setDragIndex(index);
    e.dataTransfer.effectAllowed = 'move';
  };
  const onItemDragOver = (e) => { e.preventDefault(); };

  const onItemDrop = async (e, index) => {
    e.preventDefault();
    e.stopPropagation(); // prevent bubble to onContainerDrop
    setDropErr(null);
    const assetRaw = e.dataTransfer.getData('application/x-df-asset');
    if (assetRaw) {
      try {
        const asset = JSON.parse(assetRaw);
        await poFetch('/playlists/' + playlistId + '/items', {
          method: 'POST', body: JSON.stringify({ asset_id: asset.id }),
        });
        onReload();
      } catch (err) { setDropErr(err.message || 'Failed to add clip'); }
      return;
    }
    // Reorder within the playlist.
    if (dragIndex === null || dragIndex === index) return;
    const order = items.map(i => i.id);
    const [moved] = order.splice(dragIndex, 1);
    order.splice(index, 0, moved);
    setDragIndex(null);
    try {
      await poFetch('/playlists/' + playlistId + '/reorder', {
        method: 'PUT', body: JSON.stringify({ order }),
      });
      onReload();
    } catch (err) { setDropErr(err.message || 'Failed to reorder'); }
  };

  const onContainerDrop = async (e) => {
    const assetRaw = e.dataTransfer.getData('application/x-df-asset');
    if (!assetRaw) return;
    e.preventDefault();
    setDropErr(null);
    try {
      const asset = JSON.parse(assetRaw);
      await poFetch('/playlists/' + playlistId + '/items', {
        method: 'POST', body: JSON.stringify({ asset_id: asset.id }),
      });
      onReload();
    } catch (err) { setDropErr(err.message || 'Failed to add clip'); }
  };

  const removeItem = async (id) => {
    try { await poFetch('/items/' + id, { method: 'DELETE' }); onReload(); }
    catch (err) { setDropErr(err.message || 'Failed to remove'); }
  };
  const restage = async (id) => {
    try { await poFetch('/items/' + id + '/stage', { method: 'POST' }); onReload(); }
    catch (err) { setDropErr(err.message || 'Failed to restage'); }
  };

  const totalSecs = items.reduce((sum, it) => sum + itemEffectiveDuration(it), 0);

  return (
    <div className="panel po-playlist" onDragOver={e => e.preventDefault()} onDrop={onContainerDrop}>
      <div className="po-playlist-head">
        <span className="po-section-label">Playlist</span>
        {dropErr && <span className="po-drop-err">{dropErr}</span>}
      </div>
      {items.length === 0 && (
        <div className="muted po-playlist-empty">Drag clips here to build the playlist.</div>
      )}
      {items.map((it, index) => {
        const isActive = index === activeIndex;
        const dur = itemEffectiveDuration(it);
        return (
          <div key={it.id}
            className={'po-pl-item' + (isActive ? ' po-pl-item--active' : '')}
            draggable
            onDragStart={e => onItemDragStart(e, index)}
            onDragOver={onItemDragOver}
            onDrop={e => onItemDrop(e, index)}>
            <span className="po-pl-index">
              {isActive ? <span className="po-pl-onair">▶</span> : index + 1}
            </span>
            <span className="po-pl-name">{it.clip_name || it.asset_id}</span>
            <span className="mono po-pl-dur">{fmtDuration(dur)}</span>
            <span className={'badge po-pl-badge ' + (it.media_status === 'ready' ? 'success' : it.media_status === 'staging' ? 'warn' : it.media_status === 'error' ? 'error' : 'neutral')}>
              {it.media_status}
            </span>
            {it.media_status === 'error' && (
              <button className="btn ghost xs" onClick={() => restage(it.id)}>Retry</button>
            )}
            <button className="btn ghost xs" onClick={() => removeItem(it.id)}>✕</button>
            <StagingBar status={it.media_status} />
          </div>
        );
      })}
      {items.length > 0 && (
        <div className="po-playlist-footer">
          <span className="mono muted">{items.length} clip{items.length !== 1 ? 's' : ''}</span>
          <span className="mono po-pl-total">{fmtDuration(totalSecs)} total</span>
        </div>
      )}
    </div>
  );
}

// ── Transport bar ────────────────────────────────────────────────────────────
function Transport({ channel, playlistId, items, onStatus }) {
  const [busy, setBusy] = React.useState(false);
  const act = async (fn) => { setBusy(true); try { await fn(); } catch (e) { alert(e.message); } finally { setBusy(false); } };

  const notReady = items.filter(i => i.media_status !== 'ready').length;
  const canPlay  = channel.status === 'running' && !busy && !!playlistId && notReady === 0;

  const play = () => act(async () => {
    const r = await poFetch('/channels/' + channel.id + '/play', {
      method: 'POST', body: JSON.stringify({ playlist_id: playlistId }),
    });
    onStatus && onStatus(r);
  });
  const pause  = () => act(() => poFetch('/channels/' + channel.id + '/pause',  { method: 'POST' }));
  const resume = () => act(() => poFetch('/channels/' + channel.id + '/resume', { method: 'POST' }));
  const skip   = () => act(() => poFetch('/channels/' + channel.id + '/skip',   { method: 'POST' }));
  const stopPb = () => act(() => poFetch('/channels/' + channel.id + '/stop-playback', { method: 'POST' }));

  const live = channel.status === 'running';
  return (
    <div className="po-transport">
      <button className="btn primary" disabled={!canPlay} onClick={play} title={notReady > 0 ? notReady + ' clip(s) still staging' : ''}>
        {notReady > 0 && live ? '⏳ ' + notReady + ' staging' : '▶ Play'}
      </button>
      <button className="btn ghost" disabled={!live || busy} onClick={pause}>⏸ Pause</button>
      <button className="btn ghost" disabled={!live || busy} onClick={resume}>⏵ Resume</button>
      <button className="btn ghost" disabled={!live || busy} onClick={skip}>⏭ Skip</button>
      <button className="btn danger ghost" disabled={!live || busy} onClick={stopPb}>⏹ Stop</button>
    </div>
  );
}

// ── Program monitor ──────────────────────────────────────────────────────────
function ProgramMonitor({ channel, engine }) {
  const videoRef  = React.useRef(null);
  const hlsRef    = React.useRef(null);
  const onAir     = channel.status === 'running';
  const previewUrl = `/media/live/${channel.id}/index.m3u8`;

  React.useEffect(() => {
    const vid = videoRef.current;
    if (!vid) return;

    // Tear down any previous HLS instance before re-evaluating.
    if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; }

    if (!onAir) { vid.src = ''; return; }

    if (window.Hls && window.Hls.isSupported()) {
      const hls = new window.Hls({ liveSyncDurationCount: 3, liveMaxLatencyDurationCount: 6 });
      hlsRef.current = hls;
      hls.loadSource(previewUrl);
      hls.attachMedia(vid);
      hls.on(window.Hls.Events.MANIFEST_PARSED, () => vid.play().catch(() => {}));
    } else if (vid.canPlayType('application/vnd.apple.mpegurl')) {
      // Native HLS (Safari).
      vid.src = previewUrl;
      vid.play().catch(() => {});
    }

    return () => {
      if (hlsRef.current) { hlsRef.current.destroy(); hlsRef.current = null; }
    };
  }, [onAir, channel.id]);

  return (
    <div className="po-monitor">
      <div className="po-monitor-head">
        <span className={'po-onair ' + (onAir ? 'live' : '')}>{onAir ? '● ON AIR' : '○ OFF'}</span>
        <span className="mono muted">{channel.output_type?.toUpperCase()} · {channel.video_format}</span>
      </div>
      <div className="po-monitor-screen">
        <video ref={videoRef} className="po-monitor-video" muted playsInline autoPlay />
        {!onAir && (
          <div className="po-monitor-overlay muted">Channel stopped</div>
        )}
      </div>
      {engine && (
        <div className="po-monitor-foot mono muted">
          {engine.currentClip && <span className="po-monitor-clip-name">{engine.currentClip}</span>}
          <span>clip {engine.currentIndex >= 0 ? engine.currentIndex + 1 : '–'} / {engine.playlistLength || 0}</span>
          {engine.loop && <span> · loop</span>}
        </div>
      )}
    </div>
  );
}

// ── Channel detail (monitors + bin + playlist + transport) ───────────────────
function ChannelDetail({ channel, onChannelChange }) {
  const [playlists, setPlaylists] = React.useState([]);
  const [playlistId, setPlaylistId] = React.useState(null);
  const [items, setItems] = React.useState([]);
  const [engine, setEngine] = React.useState(null);
  const [ch, setCh] = React.useState(channel);

  React.useEffect(() => { setCh(channel); }, [channel.id]);

  const loadPlaylists = React.useCallback(async () => {
    const pls = await poFetch('/playlists?channel_id=' + channel.id);
    setPlaylists(pls);
    if (pls.length && !playlistId) setPlaylistId(pls[0].id);
    if (!pls.length) {
      // Auto-create a default playlist so the operator can start dragging.
      const created = await poFetch('/playlists', {
        method: 'POST', body: JSON.stringify({ channel_id: channel.id, name: 'Main' }),
      });
      setPlaylists([created]); setPlaylistId(created.id);
    }
  }, [channel.id]);

  const loadItems = React.useCallback(async () => {
    if (!playlistId) return;
    const its = await poFetch('/playlists/' + playlistId + '/items');
    setItems(its);
  }, [playlistId]);

  React.useEffect(() => { loadPlaylists(); }, [channel.id]);
  React.useEffect(() => { loadItems(); }, [playlistId]);

  // Poll engine status + item staging while live.
  React.useEffect(() => {
    let t;
    const poll = async () => {
      try {
        const s = await poFetch('/channels/' + channel.id + '/status');
        setEngine(s.engine || null);
      } catch (_) {}
      try { await loadItems(); } catch (_) {}
      t = setTimeout(poll, 4000);
    };
    poll();
    return () => clearTimeout(t);
  }, [channel.id, playlistId]);

  const startChannel = async () => {
    const updated = await poFetch('/channels/' + channel.id + '/start', { method: 'POST' });
    setCh(updated); onChannelChange(updated);
  };
  const stopChannel = async () => {
    const updated = await poFetch('/channels/' + channel.id + '/stop', { method: 'POST' });
    setCh(updated); onChannelChange(updated);
  };
  const deleteChannel = async () => {
    if (!window.confirm('Delete channel "' + ch.name + '"? This cannot be undone.')) return;
    try {
      await poFetch('/channels/' + ch.id, { method: 'DELETE' });
      onChannelChange({ ...ch, _deleted: true });
    } catch (e) { alert(e.message); }
  };

  // engine.currentIndex maps directly to the sorted item position.
  const activeIndex = (engine && engine.currentIndex >= 0) ? engine.currentIndex : -1;

  return (
    <div className="po-detail">
      <div className="po-detail-head">
        <div>
          <h3 style={{ margin: 0 }}>{ch.name}</h3>
          <span className="mono muted">{ch.output_type?.toUpperCase()} · {ch.video_format} · {ch.status}</span>
        </div>
        <div className="po-detail-actions">
          {ch.status === 'running'
            ? <button className="btn danger" onClick={stopChannel}>Stop channel</button>
            : <button className="btn primary" onClick={startChannel}>Start channel</button>}
          {ch.status !== 'running' && (
            <button className="btn ghost danger sm" onClick={deleteChannel} title="Delete this channel">Delete</button>
          )}
        </div>
      </div>
      {ch.error_message && <div className="alert error">{ch.error_message}</div>}

      <div className="po-grid">
        <ProgramMonitor channel={ch} engine={engine} />
        <MediaBin projectId={ch.project_id} />
      </div>

      <Transport channel={ch} playlistId={playlistId} items={items} onStatus={() => loadItems()} />

      {playlistId && (
        <Playlist
          channel={ch}
          playlistId={playlistId}
          items={items}
          activeIndex={activeIndex}
          onReload={loadItems}
        />
      )}
    </div>
  );
}

// ── Top-level page ───────────────────────────────────────────────────────────
function Playout() {
  const [channels, setChannels] = React.useState(null);
  const [selectedId, setSelectedId] = React.useState(null);
  const [showCreate, setShowCreate] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const load = React.useCallback(async () => {
    try {
      const list = await poFetch('/channels');
      setChannels(list);
      if (list.length && !selectedId) setSelectedId(list[0].id);
    } catch (e) { setErr(e.message); setChannels([]); }
  }, [selectedId]);

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

  const selected = (channels || []).find(c => c.id === selectedId) || null;
  const onChannelChange = (updated) => {
    if (updated._deleted) {
      setChannels(cs => {
        const next = (cs || []).filter(c => c.id !== updated.id);
        setSelectedId(next.length ? next[0].id : null);
        return next;
      });
      return;
    }
    setChannels(cs => (cs || []).map(c => c.id === updated.id ? updated : c));
  };

  return (
    <div className="page">
      <div className="page-header">
        <span className="title">Playout — Master Control</span>
        <span className="subtitle">Schedule and play assets to SDI, NDI, SRT or RTMP.</span>
      </div>
      <div className="page-body po-page">
        {err && <div className="alert error">{err}</div>}
        <div className="po-channels-bar">
          {(channels || []).map(c => (
            <button key={c.id}
              className={'po-chan-tab ' + (c.id === selectedId ? 'active' : '')}
              onClick={() => setSelectedId(c.id)}>
              <span className={'po-chan-dot ' + (c.status === 'running' ? 'live' : '')} />
              {c.name}
            </button>
          ))}
          <button className="btn ghost sm" onClick={() => setShowCreate(true)}>+ Channel</button>
        </div>

        {channels === null && <div className="muted">Loading channels…</div>}
        {channels !== null && channels.length === 0 && (
          <div className="po-empty">
            <p className="muted">No playout channels yet.</p>
            <button className="btn primary" onClick={() => setShowCreate(true)}>Create your first channel</button>
          </div>
        )}
        {selected && <ChannelDetail key={selected.id} channel={selected} onChannelChange={onChannelChange} />}
      </div>

      {showCreate && (
        <ChannelCreate
          onClose={() => setShowCreate(false)}
          onCreated={(ch) => { setShowCreate(false); setChannels(cs => [...(cs || []), ch]); setSelectedId(ch.id); }}
        />
      )}
    </div>
  );
}

window.Playout = Playout;
