'use client';

/**
 * Jobs panel for the Forge web UI. Mirrors the extension's JobsTab —
 * list jobs, expand a job to see recent runs, expand a run to see
 * per-item dispatches. Click a dispatch's "open →" to deep-link into
 * the resulting Pipeline run (?view=pipelines&pipelineId=...).
 *
 * Read + run + toggle + delete + reset_dedup. Creation lives in the
 * extension (form is connector-aware); web is read-mostly.
 */

import { useEffect, useState, useCallback } from 'react';

interface Job {
  id: string;
  name: string;
  enabled: boolean;
  schedule_interval_minutes: number;
  source_connector: string;
  source_tool: string;
  dispatch_type: 'pipeline' | 'chat';
  last_run_at: string | null;
  next_run_at: string | null;
}

interface JobRun {
  id: string;
  job_id: string;
  started_at: string;
  finished_at: string | null;
  status: 'running' | 'ok' | 'error';
  items_seen: number;
  items_new: number;
  items_dispatched: number;
  error: string | null;
  trigger: 'schedule' | 'manual';
  notes?: string | null;
  log?: string | null;
}

interface JobDispatch {
  id: string;
  item_key: string;
  item_preview: string | null;
  dispatch_type: 'pipeline' | 'chat';
  dispatch_target_id: string | null;
  status: 'dispatched' | 'error';
  error: string | null;
  created_at: string;
}

interface Props {
  /** Switch the dashboard to the pipelines view for a specific run. */
  onViewPipeline?: (pipelineId: string) => void;
}

/**
 * Dispatch the Dashboard nav event to jump to the Logs view with a
 * pre-filled search string. Used by the "view logs" buttons here.
 */
function navigateLogs(search: string) {
  window.dispatchEvent(new CustomEvent('forge:navigate', { detail: { view: 'logs', logSearch: search } }));
}

export default function JobsView({ onViewPipeline }: Props) {
  const [jobs, setJobs] = useState<Job[] | null>(null);
  const [err, setErr] = useState('');
  const [expandedJobId, setExpandedJobId] = useState<string>('');
  const [runsByJob, setRunsByJob] = useState<Record<string, JobRun[]>>({});

  const refresh = useCallback(async () => {
    try {
      const r = await fetch('/api/jobs');
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const j = await r.json();
      setJobs(j.jobs || []);
      setErr('');
    } catch (e) {
      setErr(e instanceof Error ? e.message : String(e));
      setJobs([]);
    }
  }, []);

  useEffect(() => { void refresh(); }, [refresh]);

  async function loadRuns(jobId: string) {
    try {
      const r = await fetch(`/api/jobs/${encodeURIComponent(jobId)}/runs?limit=20`);
      const j = await r.json();
      setRunsByJob((prev) => ({ ...prev, [jobId]: j.runs || [] }));
    } catch {}
  }

  function expand(id: string) {
    if (expandedJobId === id) { setExpandedJobId(''); return; }
    setExpandedJobId(id);
    if (!runsByJob[id]) void loadRuns(id);
  }

  async function fireNow(id: string, opts?: { resetDedup?: boolean }) {
    try {
      const qs = opts?.resetDedup ? '?reset_dedup=1' : '';
      await fetch(`/api/jobs/${encodeURIComponent(id)}/run${qs}`, { method: 'POST' });
      await loadRuns(id);
    } catch (e) { alert(`Fire failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function toggle(j: Job) {
    try {
      await fetch(`/api/jobs/${encodeURIComponent(j.id)}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled: !j.enabled }),
      });
      await refresh();
    } catch (e) { alert(`Toggle failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function remove(id: string) {
    if (!confirm('Delete this job? (cascades runs + dedup)')) return;
    try {
      await fetch(`/api/jobs/${encodeURIComponent(id)}`, { method: 'DELETE' });
      await refresh();
    } catch (e) { alert(`Delete failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  async function resetDedup(id: string) {
    if (!confirm('Wipe dedup state? Next tick will re-process every item.')) return;
    try {
      const r = await fetch(`/api/jobs/${encodeURIComponent(id)}/reset_dedup`, { method: 'POST' });
      const j = await r.json();
      alert(`Removed ${j.removed} dedup keys`);
    } catch (e) { alert(`Reset failed: ${e instanceof Error ? e.message : String(e)}`); }
  }

  return (
    <div className="flex-1 flex flex-col min-h-0 overflow-auto p-4">
      <div className="flex items-center justify-between mb-3">
        <div>
          <h2 className="text-sm font-semibold text-[var(--text-primary)]">Jobs</h2>
          <p className="text-[11px] text-[var(--text-secondary)] mt-0.5">
            Scheduled connector polls — each new item fans out to a Pipeline run or a Chat session.
            Create + edit via the Forge extension (Jobs tab).
          </p>
        </div>
        <div className="flex items-center gap-2">
          <button
            onClick={() => navigateLogs('[jobs]')}
            className="text-[11px] px-2 py-1 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
            title="Tail forge.log filtered to [jobs] lines"
          >
            View all job logs →
          </button>
          <button
            onClick={() => void refresh()}
            className="text-[11px] px-2 py-1 border border-[var(--border)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
          >
            Refresh
          </button>
        </div>
      </div>

      {err && <div className="text-[11px] text-[var(--red)] bg-[var(--red)]/10 rounded p-2 mb-3">{err}</div>}

      {jobs == null ? (
        <div className="text-[11px] text-[var(--text-secondary)] text-center py-8">Loading…</div>
      ) : jobs.length === 0 ? (
        <div className="text-[11px] text-[var(--text-secondary)] text-center py-8">
          (no jobs · open the Forge extension → Jobs tab → + New job)
        </div>
      ) : (
        <div className="space-y-2">
          {jobs.map((j) => (
            <JobRow
              key={j.id}
              job={j}
              expanded={expandedJobId === j.id}
              runs={runsByJob[j.id]}
              onClick={() => expand(j.id)}
              onFire={() => fireNow(j.id)}
              onForceFire={() => fireNow(j.id, { resetDedup: true })}
              onToggle={() => toggle(j)}
              onDelete={() => remove(j.id)}
              onReset={() => resetDedup(j.id)}
              onViewPipeline={onViewPipeline}
            />
          ))}
        </div>
      )}
    </div>
  );
}

function JobRow({ job, expanded, runs, onClick, onFire, onForceFire, onToggle, onDelete, onReset, onViewPipeline }: {
  job: Job; expanded: boolean; runs?: JobRun[];
  onClick: () => void; onFire: () => void; onForceFire: () => void; onToggle: () => void; onDelete: () => void; onReset: () => void;
  onViewPipeline?: (id: string) => void;
}) {
  return (
    <div className="rounded border border-[var(--border)] bg-[var(--bg-secondary)]">
      <div onClick={onClick} className="px-3 py-2 cursor-pointer flex items-baseline gap-2">
        <span className="text-[10px] text-[var(--text-secondary)] w-3">{expanded ? '▾' : '▸'}</span>
        <div className="flex-1 min-w-0">
          <div className="flex items-baseline gap-2">
            <span className="text-[12px] font-semibold text-[var(--text-primary)] truncate">{job.name}</span>
            <span className={`text-[9px] px-1.5 py-0.5 rounded ${job.enabled ? 'bg-[var(--green)]/15 text-[var(--green)]' : 'bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]'}`}>
              {job.enabled ? 'enabled' : 'disabled'}
            </span>
          </div>
          <div className="text-[10px] text-[var(--text-secondary)] font-mono mt-0.5 truncate">
            {job.source_connector}.{job.source_tool} → {job.dispatch_type} · every {job.schedule_interval_minutes}m
          </div>
          <div className="text-[10px] text-[var(--text-secondary)] mt-0.5">
            {job.last_run_at ? `last ${job.last_run_at}` : 'never run'} · {job.next_run_at ? `next ${job.next_run_at}` : 'next: pending'}
          </div>
        </div>
      </div>
      {expanded && (
        <div className="border-t border-[var(--border)] px-3 py-2 bg-[var(--bg-tertiary)]">
          <div className="flex items-center gap-2 mb-2">
            <button onClick={(e) => { e.stopPropagation(); onFire(); }} className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]">Run now</button>
            <button onClick={(e) => { e.stopPropagation(); onForceFire(); }} className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]" title="Reset dedup + run — re-dispatch every item this tick">Force run</button>
            <button onClick={(e) => { e.stopPropagation(); onToggle(); }} className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]">{job.enabled ? 'Disable' : 'Enable'}</button>
            <button onClick={(e) => { e.stopPropagation(); onReset(); }} className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]" title="Wipe dedup state">Reset dedup</button>
            <button onClick={(e) => { e.stopPropagation(); navigateLogs(`[jobs] ${job.id}`); }} className="text-[10px] px-2 py-0.5 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]" title="Tail forge.log filtered to this job's id">View logs</button>
            <span className="flex-1" />
            <button onClick={(e) => { e.stopPropagation(); onDelete(); }} className="text-[10px] px-2 py-0.5 border border-[var(--red)]/50 text-[var(--red)] rounded hover:bg-[var(--red)]/10">Delete</button>
          </div>
          <div className="text-[10px] font-semibold text-[var(--text-secondary)] uppercase mb-1">Recent runs</div>
          {runs == null ? (
            <div className="text-[10px] text-[var(--text-secondary)]">Loading…</div>
          ) : runs.length === 0 ? (
            <div className="text-[10px] text-[var(--text-secondary)]">(no runs yet)</div>
          ) : (
            <div className="space-y-0.5">
              {runs.map((r) => (
                <RunRow key={r.id} jobId={job.id} run={r} onViewPipeline={onViewPipeline} />
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function RunRow({ jobId, run, onViewPipeline }: { jobId: string; run: JobRun; onViewPipeline?: (id: string) => void }) {
  const [expanded, setExpanded] = useState(false);
  const [dispatches, setDispatches] = useState<JobDispatch[] | null>(null);
  const [loading, setLoading] = useState(false);
  const [showLog, setShowLog] = useState(false);

  async function load() {
    if (dispatches != null || loading) return;
    setLoading(true);
    try {
      const r = await fetch(`/api/jobs/${encodeURIComponent(jobId)}/runs/${encodeURIComponent(run.id)}`);
      const j = await r.json();
      setDispatches(j.dispatches || []);
    } catch {} finally { setLoading(false); }
  }

  function onToggle() {
    const next = !expanded;
    setExpanded(next);
    if (next) void load();
  }

  async function copyLog() {
    if (!run.log) return;
    try { await navigator.clipboard.writeText(run.log); } catch {}
  }

  const dur = run.finished_at && run.started_at
    ? `${Math.round((new Date(run.finished_at).getTime() - new Date(run.started_at).getTime()) / 100) / 10}s`
    : '…';
  const color = run.status === 'ok' ? 'var(--green)' : run.status === 'error' ? 'var(--red)' : 'var(--text-secondary)';

  return (
    <div className="text-[10.5px]">
      <div onClick={onToggle} className="cursor-pointer flex items-baseline gap-1.5 py-0.5">
        <span className="text-[9px] text-[var(--text-secondary)] w-3">{expanded ? '▾' : '▸'}</span>
        <span className="font-mono text-[var(--text-secondary)]">{run.started_at}</span>
        <span style={{ color }}>{run.status}</span>
        <span className="text-[var(--text-secondary)]">seen={run.items_seen} new={run.items_new} dispatched={run.items_dispatched}</span>
        <span className="text-[var(--text-secondary)]">{dur}</span>
        {run.trigger === 'manual' && <span className="text-[9px] px-1 rounded bg-[var(--text-secondary)]/15 text-[var(--text-secondary)]">manual</span>}
      </div>
      {run.error && <div className="font-mono text-[10px] text-[var(--red)] pl-5 py-0.5">{run.error.slice(0, 300)}</div>}
      {run.notes && <div className="text-[10px] text-[var(--text-secondary)] pl-5 py-0.5 italic">{run.notes}</div>}
      {expanded && (
        <div className="pl-5 pr-1 pb-1 pt-0.5 space-y-0.5 border-l border-[var(--border)] ml-1.5">
          <div className="flex items-center gap-2 py-0.5">
            <span className="text-[10px] text-[var(--text-secondary)] uppercase">Dispatches</span>
            <span className="flex-1" />
            {run.log && (
              <>
                <button
                  onClick={() => setShowLog((v) => !v)}
                  className="text-[10px] px-1.5 py-0 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]"
                >
                  {showLog ? 'hide log' : `show log (${run.log.split('\n').length} lines)`}
                </button>
                {showLog && (
                  <button onClick={copyLog} className="text-[10px] px-1.5 py-0 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]">copy</button>
                )}
              </>
            )}
          </div>
          {showLog && run.log && (
            <pre className="text-[10px] font-mono whitespace-pre-wrap leading-snug p-2 bg-black/30 text-[var(--text-secondary)] border border-[var(--border)] rounded max-h-80 overflow-auto">
              {run.log}
            </pre>
          )}
          {loading ? (
            <div className="text-[10px] text-[var(--text-secondary)]">Loading dispatches…</div>
          ) : dispatches == null || dispatches.length === 0 ? (
            <div className="text-[10px] text-[var(--text-secondary)]">(no dispatches)</div>
          ) : (
            dispatches.map((d) => <DispatchRow key={d.id} d={d} onViewPipeline={onViewPipeline} />)
          )}
        </div>
      )}
    </div>
  );
}

function DispatchRow({ d, onViewPipeline }: { d: JobDispatch; onViewPipeline?: (id: string) => void }) {
  function open() {
    if (!d.dispatch_target_id) return;
    if (d.dispatch_type === 'pipeline' && onViewPipeline) {
      onViewPipeline(d.dispatch_target_id);
    } else if (d.dispatch_type === 'chat') {
      // No web chat surface yet — copy session id for use in the extension.
      navigator.clipboard?.writeText(d.dispatch_target_id).catch(() => {});
    }
  }

  const statusColor = d.status === 'dispatched' ? 'var(--green)' : 'var(--red)';

  return (
    <div className="text-[10px] py-0.5">
      <div className="flex items-center gap-1.5">
        <span className="font-mono text-[var(--text-secondary)] w-16 shrink-0 truncate">{d.item_key}</span>
        <span className="flex-1 min-w-0 truncate text-[var(--text-primary)]">{d.item_preview || '(no preview)'}</span>
        <span style={{ color: statusColor }} className="text-[9px]">{d.status}</span>
        {d.dispatch_target_id && (
          <button
            onClick={open}
            className="text-[9px] px-1 border border-[var(--border)] rounded hover:bg-[var(--bg-secondary)]"
            title={d.dispatch_type === 'pipeline' ? 'Open pipeline run' : 'Copy chat session id'}
          >
            {d.dispatch_type === 'pipeline' ? 'open →' : 'copy id'}
          </button>
        )}
      </div>
      {d.error && <div className="font-mono text-[9.5px] text-[var(--red)] pl-1">{d.error.slice(0, 200)}</div>}
    </div>
  );
}
