<script lang="ts">
	import { page } from '$app/state';
	import { getContext } from 'svelte';
	import {
		api,
		ApiError,
		type WorkflowSummary,
		type WorkflowRun,
		type WorkflowRunDetail,
		type WorkflowRunStep
	} from '$lib/api';
	import { APP_CTX_KEY, type AppContext } from '../+layout.svelte';

	let slug = $derived(page.params.slug ?? '');
	const ctx = getContext<AppContext>(APP_CTX_KEY);
	const appStore = ctx?.app;
	let appName = $derived($appStore?.name ?? slug);

	let workflows = $state<WorkflowSummary[]>([]);
	let selectedWf = $state<string | null>(null);
	let runs = $state<WorkflowRun[]>([]);
	let detail = $state<WorkflowRunDetail | null>(null);
	let error = $state<string | null>(null);
	let loading = $state(false);

	async function loadWorkflows() {
		loading = true;
		error = null;
		try {
			workflows = await api.workflows.list(slug);
		} catch (e) {
			error = e instanceof ApiError ? e.message : String(e);
		} finally {
			loading = false;
		}
	}

	async function selectWorkflow(name: string) {
		selectedWf = name;
		detail = null;
		runs = [];
		error = null;
		try {
			runs = await api.workflows.runs(slug, name);
		} catch (e) {
			error = e instanceof ApiError ? e.message : String(e);
		}
	}

	async function selectRun(runId: string) {
		error = null;
		try {
			detail = await api.workflows.run(slug, runId);
		} catch (e) {
			error = e instanceof ApiError ? e.message : String(e);
		}
	}

	async function startRun() {
		if (!selectedWf) return;
		error = null;
		try {
			const run = await api.workflows.start(slug, selectedWf, null);
			await selectWorkflow(selectedWf);
			await selectRun(run.id);
		} catch (e) {
			error = e instanceof ApiError ? e.message : String(e);
		}
	}

	$effect(() => {
		void slug;
		void loadWorkflows();
	});

	// --- static depth-1 DAG layout: layered by longest-path level ---------
	const COL_W = 170;
	const ROW_H = 64;
	const NODE_W = 140;
	const NODE_H = 42;

	let layout = $derived(computeLayout(detail?.steps ?? []));

	function computeLayout(steps: WorkflowRunStep[]) {
		const byName = new Map(steps.map((s) => [s.name, s]));
		const level = new Map<string, number>();
		function lvl(name: string, seen: Set<string> = new Set()): number {
			const cached = level.get(name);
			if (cached !== undefined) return cached;
			if (seen.has(name)) return 0; // cycle guard (validation forbids these)
			seen.add(name);
			const deps = byName.get(name)?.depends_on ?? [];
			const v = deps.length === 0 ? 0 : Math.max(...deps.map((d) => lvl(d, seen) + 1));
			level.set(name, v);
			return v;
		}
		for (const s of steps) lvl(s.name);

		const cols = new Map<number, WorkflowRunStep[]>();
		for (const s of steps) {
			const l = level.get(s.name) ?? 0;
			(cols.get(l) ?? cols.set(l, []).get(l)!).push(s);
		}
		const pos = new Map<string, { x: number; y: number }>();
		const nodes: { step: WorkflowRunStep; x: number; y: number }[] = [];
		for (const [l, arr] of [...cols.entries()].sort((a, b) => a[0] - b[0])) {
			arr.forEach((s, i) => {
				const x = l * COL_W + 10;
				const y = i * ROW_H + 10;
				nodes.push({ step: s, x, y });
				pos.set(s.name, { x, y });
			});
		}
		const edges: { x1: number; y1: number; x2: number; y2: number }[] = [];
		for (const s of steps) {
			for (const d of s.depends_on) {
				const from = pos.get(d);
				const to = pos.get(s.name);
				if (from && to) {
					edges.push({
						x1: from.x + NODE_W,
						y1: from.y + NODE_H / 2,
						x2: to.x,
						y2: to.y + NODE_H / 2
					});
				}
			}
		}
		const maxLevel = Math.max(0, ...[...cols.keys()]);
		const maxRows = Math.max(1, ...[...cols.values()].map((a) => a.length));
		return {
			nodes,
			edges,
			width: (maxLevel + 1) * COL_W + 20,
			height: maxRows * ROW_H + 20
		};
	}

	function short(id: string) {
		return id.slice(0, 8);
	}
</script>

<svelte:head>
	<title>Workflows — {appName} — PiCloud</title>
</svelte:head>

<header class="panel-head">
	<h2>Workflows</h2>
	<p class="subtle">
		Durable DAG workflows (v1.2). Definitions are authored declaratively
		(<code>[[workflows]]</code> in <code>picloud.toml</code>); start runs and
		inspect their per-step progress here.
	</p>
	<div class="toolbar">
		<button type="button" class="secondary" onclick={() => loadWorkflows()} disabled={loading}>
			{loading ? 'Refreshing…' : 'Refresh'}
		</button>
	</div>
</header>

{#if error}
	<p class="error">{error}</p>
{/if}

{#if loading && workflows.length === 0}
	<p class="muted">Loading…</p>
{:else if workflows.length === 0}
	<p class="muted" data-testid="workflows-empty-state">
		No workflows in this app yet. Author one with a <code>[[workflows]]</code>
		block in your manifest and <code>pic apply</code>.
	</p>
{:else}
	<table class="grid" data-testid="workflows-table">
		<thead>
			<tr><th>Workflow</th><th class="num">Steps</th><th>Enabled</th><th></th></tr>
		</thead>
		<tbody>
			{#each workflows as w (w.name)}
				<tr class:selected={selectedWf === w.name}>
					<td><code>{w.name}</code></td>
					<td class="num">{w.steps}</td>
					<td>{w.enabled ? 'yes' : 'no'}</td>
					<td>
						<button type="button" class="link" onclick={() => selectWorkflow(w.name)}>
							runs →
						</button>
					</td>
				</tr>
			{/each}
		</tbody>
	</table>
{/if}

{#if selectedWf}
	<section class="runs-panel">
		<div class="runs-head">
			<h3>Runs — <code>{selectedWf}</code></h3>
			<button type="button" class="secondary" onclick={() => startRun()}>Start run</button>
		</div>
		{#if runs.length === 0}
			<p class="muted">No runs yet.</p>
		{:else}
			<table class="grid" data-testid="runs-table">
				<thead>
					<tr><th>Run</th><th>Status</th><th class="num">Depth</th><th>Created</th><th></th></tr>
				</thead>
				<tbody>
					{#each runs as r (r.id)}
						<tr class:selected={detail?.run.id === r.id}>
							<td><code>{short(r.id)}</code></td>
							<td><span class="badge st-{r.status}">{r.status}</span></td>
							<td class="num">{r.workflow_depth}</td>
							<td class="muted">{r.created_at}</td>
							<td>
								<button type="button" class="link" onclick={() => selectRun(r.id)}>detail →</button>
							</td>
						</tr>
					{/each}
				</tbody>
			</table>
		{/if}
	</section>
{/if}

{#if detail}
	<section class="detail-panel" data-testid="run-detail">
		<h3>
			Run <code>{short(detail.run.id)}</code>
			<span class="badge st-{detail.run.status}">{detail.run.status}</span>
		</h3>
		{#if detail.run.error}
			<p class="error">{detail.run.error}</p>
		{/if}

		<h4>DAG</h4>
		<div class="dag-wrap">
			<svg width={layout.width} height={layout.height} role="img" aria-label="workflow DAG">
				{#each layout.edges as e}
					<line
						x1={e.x1}
						y1={e.y1}
						x2={e.x2}
						y2={e.y2}
						stroke="var(--border)"
						stroke-width="1.5"
						marker-end="url(#arrow)"
					/>
				{/each}
				<defs>
					<marker id="arrow" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto">
						<path d="M0,0 L7,3 L0,6 Z" fill="var(--text-muted)" />
					</marker>
				</defs>
				{#each layout.nodes as n (n.step.name)}
					<g transform="translate({n.x},{n.y})">
						<rect
							width={NODE_W}
							height={NODE_H}
							rx="6"
							class="node st-{n.step.status}"
						/>
						<text x={NODE_W / 2} y={NODE_H / 2 + 4} text-anchor="middle" class="node-label">
							{n.step.name}
						</text>
					</g>
				{/each}
			</svg>
		</div>

		<h4>Steps</h4>
		<table class="grid" data-testid="steps-table">
			<thead>
				<tr><th>Step</th><th>Status</th><th class="num">Attempt</th><th>Depends on</th><th>Error</th></tr>
			</thead>
			<tbody>
				{#each detail.steps as s (s.name)}
					<tr>
						<td><code>{s.name}</code></td>
						<td><span class="badge st-{s.status}">{s.status}</span></td>
						<td class="num">{s.attempt}/{s.max_attempts}</td>
						<td class="muted">{s.depends_on.join(', ')}</td>
						<td class="err-cell">{s.error ?? ''}</td>
					</tr>
				{/each}
			</tbody>
		</table>
	</section>
{/if}

<style>
	.panel-head {
		margin-bottom: 1rem;
	}
	h2 {
		margin: 0.25rem 0;
		font-size: 1.125rem;
	}
	h3 {
		font-size: 1rem;
		margin: 0;
	}
	h4 {
		font-size: 0.85rem;
		color: var(--text-muted);
		margin: 1rem 0 0.4rem;
		text-transform: uppercase;
		letter-spacing: 0.04em;
	}
	.subtle {
		color: var(--text-muted);
		font-size: 0.875rem;
	}
	.toolbar {
		display: flex;
		gap: 0.75rem;
		align-items: center;
		margin-top: 0.75rem;
	}
	button.secondary {
		background: transparent;
		color: var(--text-muted);
		border: 1px solid var(--border);
		padding: 0.4rem 0.85rem;
		border-radius: var(--radius-md);
		cursor: pointer;
		font-size: 0.85rem;
	}
	button.secondary:hover:not(:disabled) {
		background: var(--bg-elevated);
		color: var(--text-primary);
	}
	button.link {
		background: none;
		border: none;
		color: var(--color-accent, #4f8cff);
		cursor: pointer;
		padding: 0;
		font-size: 0.85rem;
	}
	.error {
		color: var(--color-danger);
	}
	.muted {
		color: var(--text-muted);
	}
	table.grid {
		width: 100%;
		border-collapse: collapse;
		margin-top: 0.5rem;
	}
	table.grid th,
	table.grid td {
		text-align: left;
		padding: 0.5rem;
		border-bottom: 1px solid var(--border);
	}
	.num {
		text-align: right;
		font-variant-numeric: tabular-nums;
	}
	tr.selected {
		background: var(--bg-elevated);
	}
	.err-cell {
		color: var(--color-danger);
		font-size: 0.8rem;
		max-width: 24rem;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}
	.runs-panel,
	.detail-panel {
		margin-top: 1.5rem;
		padding-top: 1rem;
		border-top: 1px solid var(--border);
	}
	.runs-head {
		display: flex;
		align-items: center;
		justify-content: space-between;
	}
	.dag-wrap {
		overflow: auto;
		border: 1px solid var(--border);
		border-radius: var(--radius-md);
		padding: 0.5rem;
		background: var(--bg-elevated);
	}
	.node {
		fill: var(--bg-surface, #1b1e26);
		stroke: var(--border);
		stroke-width: 1.5;
	}
	.node-label {
		fill: var(--text-primary);
		font-size: 12px;
		font-family: var(--font-mono, monospace);
	}
	/* Status colors, shared by badges + DAG node strokes. */
	.badge {
		display: inline-block;
		padding: 0.1rem 0.5rem;
		border-radius: 999px;
		font-size: 0.75rem;
		border: 1px solid var(--border);
	}
	.st-succeeded {
		color: #2ecc71;
		stroke: #2ecc71;
	}
	.st-failed {
		color: #e74c3c;
		stroke: #e74c3c;
	}
	.st-running {
		color: #4f8cff;
		stroke: #4f8cff;
	}
	.st-ready {
		color: #f0ad4e;
		stroke: #f0ad4e;
	}
	.st-skipped {
		color: var(--text-muted);
		stroke: var(--text-muted);
	}
	.st-pending,
	.st-canceled {
		color: var(--text-muted);
	}
</style>
