"use client";

import { useEffect, useRef, useState } from "react";
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { api, Workflow, WorkflowStep, WorkflowStepRef, SecretGroupSummary } from "@/lib/api";
import { Button } from "@/components/ui";
import { EditWorkflowModal } from "@/components/workflows/EditWorkflowModal";
import { StepPickerModal } from "@/components/workflows/StepPickerModal";
import { resolveTargets } from "@/lib/targets";

const inputClass = "w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary placeholder-text-secondary/50 focus:border-signal focus:outline-none focus:ring-1 focus:ring-signal";

type DragPayload = { kind: "lib"; stepId: string } | { kind: "move"; from: number };

function ShellBadge({ interpreter }: { interpreter: "bash" | "powershell" }) {
    const isBash = interpreter === "bash";
    return <span className={`rounded px-1.5 py-0.5 font-mono text-[10px] uppercase ${isBash ? "bg-bash/15 text-bash" : "bg-pwsh/15 text-pwsh"}`}>{isBash ? "bash" : "pwsh"}</span>;
}

function AdhocBadge() {
    return <span className="rounded px-1.5 py-0.5 font-mono text-[10px] uppercase bg-signal/15 text-signal">ad-hoc</span>;
}

function snapshotOf(w: Workflow): string {
    return JSON.stringify({
        name: w.name,
        target_server_ids: w.target_server_ids,
        target_tags: w.target_tags ?? {},
        steps: w.steps,
    });
}

function timeAgo(date: Date): string {
    const s = Math.floor((Date.now() - date.getTime()) / 1000);
    if (s < 5) return "just now";
    if (s < 60) return `${s}s ago`;
    const m = Math.floor(s / 60);
    if (m < 60) return `${m}m ago`;
    const h = Math.floor(m / 60);
    return `${h}h ago`;
}

export default function WorkflowBuilder() {
    const params = useParams<{ id: string }>();
    const id = params.id;
    const router = useRouter();

    const [wf, setWf] = useState<Workflow | null>(null);
    const [selected, setSelected] = useState<number | null>(null);
    const [saving, setSaving] = useState(false);
    const [lastSaved, setLastSaved] = useState<Date | null>(null);
    const [, setTick] = useState(0);
    const savedSnapshotRef = useRef<string | null>(null);
    const savingRef = useRef(false);
    const wfRef = useRef<Workflow | null>(null);
    const [running, setRunning] = useState(false);
    const [error, setError] = useState<string | null>(null);
    const [notice, setNotice] = useState<string | null>(null);
    const [importingInline, setImportingInline] = useState(false);
    const [groupKeys, setGroupKeys] = useState<Record<string, string[]>>({});
    const [editWorkflowOpen, setEditWorkflowOpen] = useState(false);
    const [dragOverZone, setDragOverZone] = useState<number | null>(null);
    const [pickerOpen, setPickerOpen] = useState(false);

    const { data: loaded } = useQuery({
        queryKey: ["workflow", id],
        queryFn: () => api.getWorkflow(id),
    });
    const { data: library } = useQuery({ queryKey: ["steps"], queryFn: api.listSteps });
    // The whole fleet, so the "runs on N servers" readout can be computed in the
    // browser rather than asking the server to resolve targets on every keystroke.
    const { data: servers } = useQuery({ queryKey: ["servers"], queryFn: () => api.listServers() });
    const { data: secretGroups } = useQuery({
        queryKey: ["secret-groups"],
        queryFn: api.listSecretGroups,
    });

    useEffect(() => {
        if (loaded && !wf) {
            setWf(loaded);
            savedSnapshotRef.current = snapshotOf(loaded);
        }
    }, [loaded]);

    useEffect(() => {
        if (!secretGroups) return;
        secretGroups.forEach((g: SecretGroupSummary) => {
            if (groupKeys[g.group] !== undefined) return;
            api.getSecretGroup(g.group)
                .then((res) =>
                    setGroupKeys((prev) => ({
                        ...prev,
                        [g.group]: res.secrets.map((s) => s.key),
                    })),
                )
                .catch(() => {
                    setGroupKeys((prev) => ({ ...prev, [g.group]: [] }));
                });
        });
    }, [secretGroups]);

    wfRef.current = wf;

    useEffect(() => {
        if (!wf || savedSnapshotRef.current === null) return;
        if (snapshotOf(wf) === savedSnapshotRef.current) return;
        const t = setTimeout(() => {
            save();
        }, 800);
        return () => clearTimeout(t);
    }, [wf]);

    useEffect(() => {
        if (!lastSaved) return;
        const iv = setInterval(() => setTick((n) => n + 1), 15000);
        return () => clearInterval(iv);
    }, [lastSaved]);

    if (!wf) {
        return <div className="p-4 text-text-secondary sm:p-6 lg:p-8">Loading…</div>;
    }

    const libById = (sid?: string) => (sid ? library?.find((l) => l.step_id === sid) : undefined);

    const targetTags = wf.target_tags ?? {};

    const matched = resolveTargets(servers ?? [], wf.target_server_ids, targetTags);

    const sortedSteps = [...wf.steps].sort((a, b) => a.order - b.order);
    const selectedRef = selected !== null ? sortedSteps[selected] : null;
    const selectedLib = selectedRef ? libById(selectedRef.step_id) : null;
    const selectedIdxInWf = selectedRef ? wf.steps.indexOf(selectedRef) : -1;

    const save = async () => {
        if (savingRef.current) return;
        const current = wfRef.current;
        if (!current) return;
        const snapshot = snapshotOf(current);
        if (snapshot === savedSnapshotRef.current) return;
        savingRef.current = true;
        setSaving(true);
        setError(null);
        try {
            const updated = await api.updateWorkflow(id, current);
            if (!updated || !Array.isArray(updated.steps)) {
                setError("Save failed: server returned an unexpected response.");
                return;
            }
            if (wfRef.current && snapshotOf(wfRef.current) === snapshot) {
                savedSnapshotRef.current = snapshotOf(updated);
                setWf(updated);
            } else {
                savedSnapshotRef.current = snapshot;
            }
            setLastSaved(new Date());
        } catch (e) {
            setError((e as Error).message);
        } finally {
            savingRef.current = false;
            setSaving(false);

            if (wfRef.current && snapshotOf(wfRef.current) !== savedSnapshotRef.current) {
                setTimeout(() => save(), 0);
            }
        }
    };

    const run = async () => {
        setRunning(true);
        setError(null);
        try {
            const { run_id } = await api.runWorkflow(id);
            router.push(`/workflows/${id}/runs/${run_id}`);
        } catch (e) {
            setError((e as Error).message);
            setRunning(false);
        }
    };

    const resequence = (steps: WorkflowStepRef[]) => steps.map((r, i) => ({ ...r, order: i }));

    const insertLibStep = (stepId: string, pos: number) => {
        const next = [...sortedSteps];
        next.splice(pos, 0, { step_id: stepId, order: 0, on_failure: "stop", max_retries: 0 });
        setWf({ ...wf, steps: resequence(next) });
    };

    const appendRef = (ref: WorkflowStepRef) => {
        setWf({ ...wf, steps: resequence([...sortedSteps, ref]) });
    };

    const addAdhocStep = () => {
        appendRef({
            inline: {
                step_id: "",
                name: "New ad-hoc step",
                description: "",
                interpreter: "bash",
                script: "",
                declared_outputs: [],
                declared_inputs: [],
                secret_refs: [],
            },
            order: wf.steps.length,
            on_failure: "stop",
            max_retries: 0,
        });
    };

    const moveStep = (from: number, pos: number) => {
        const next = [...sortedSteps];
        const [item] = next.splice(from, 1);
        const target = from < pos ? pos - 1 : pos;
        next.splice(target, 0, item);
        setWf({ ...wf, steps: resequence(next) });
        if (selected === from) setSelected(target);
        else if (selected !== null) {
            if (from < selected && target >= selected) setSelected(selected - 1);
            else if (from > selected && target <= selected) setSelected(selected + 1);
        }
    };

    const handleDrop = (e: React.DragEvent, pos: number) => {
        e.preventDefault();
        setDragOverZone(null);
        const raw = e.dataTransfer.getData("text/plain");
        if (!raw) return;
        let payload: DragPayload;
        try {
            payload = JSON.parse(raw);
        } catch {
            return;
        }
        if (payload.kind === "lib") {
            insertLibStep(payload.stepId, pos);
        } else if (payload.kind === "move") {
            moveStep(payload.from, pos);
        }
    };

    const updateRef = (idx: number, patch: Partial<WorkflowStepRef>) =>
        setWf({
            ...wf,
            steps: wf.steps.map((r, i) => (i === idx ? { ...r, ...patch } : r)),
        });

    const updateInline = (idx: number, patch: Partial<WorkflowStep>) =>
        setWf({
            ...wf,
            steps: wf.steps.map((r, i) => (i === idx && r.inline ? { ...r, inline: { ...r.inline, ...patch } } : r)),
        });

    const removeStep = (idx: number) => {
        const remaining = resequence(wf.steps.filter((_, i) => i !== idx));
        setWf({ ...wf, steps: remaining });
        setSelected(null);
    };

    const toggleSecretRef = (ref: string) => {
        if (selectedIdxInWf === -1 || !selectedRef) return;
        if (selectedRef.inline) {
            const current = selectedRef.inline.secret_refs ?? [];
            const next = current.includes(ref) ? current.filter((r) => r !== ref) : [...current, ref];
            updateInline(selectedIdxInWf, { secret_refs: next });
            return;
        }
        const current = selectedRef.overrides?.secret_refs ?? [];
        const next = current.includes(ref) ? current.filter((r) => r !== ref) : [...current, ref];
        updateRef(selectedIdxInWf, { overrides: { ...selectedRef.overrides, secret_refs: next } });
    };

    const upstreamOutputsFor = (i: number) => Array.from(new Set(sortedSteps.slice(0, i).flatMap((r) => r.inline?.declared_outputs ?? libById(r.step_id)?.declared_outputs ?? [])));

    const DropZone = ({ pos }: { pos: number }) => (
        <div
            className={`h-3 w-full transition-all ${dragOverZone === pos ? "h-8 rounded bg-signal/15 border border-dashed border-signal/50" : ""}`}
            onDragOver={(e) => {
                e.preventDefault();
                setDragOverZone(pos);
            }}
            onDragLeave={() => setDragOverZone((z) => (z === pos ? null : z))}
            onDrop={(e) => handleDrop(e, pos)}
        />
    );

    return (
        <>
            <div className="flex flex-wrap items-center gap-3 border-b border-border bg-surface px-4 py-3">
                <span className="h-2.5 w-2.5 rounded-full bg-signal" />
                <div className="flex items-center gap-1.5 text-sm">
                    <span className="text-text-secondary">Workflows /</span>
                    <span className="font-medium text-text-primary">{wf.name}</span>
                    <span className="text-text-secondary">· {saving ? "Saving…" : lastSaved ? `Saved ${timeAgo(lastSaved)}` : ""}</span>
                </div>
                <div className="ml-auto flex flex-wrap items-center gap-2">
                    <span className="rounded-full border border-border bg-surface-2 px-3 py-1 text-xs text-text-secondary">{matched.length} servers</span>
                    <Link href={`/workflows/${id}/runs`} className="rounded-lg border border-border bg-surface-2 px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary">
                        Runs
                    </Link>
                    <Button variant="secondary" size="sm" onClick={() => setEditWorkflowOpen(true)}>
                        Edit
                    </Button>
                    <Button size="sm" loading={running} onClick={run} className="bg-signal text-signal-ink border-transparent hover:bg-signal/90">
                        Run workflow
                    </Button>
                </div>
            </div>

            {error && <div className="border-b border-danger/30 bg-danger/10 px-4 py-2 text-sm text-danger">{error}</div>}
            {notice && <div className="border-b border-signal/30 bg-signal/10 px-4 py-2 text-sm text-signal">{notice}</div>}

            <div className="flex flex-1 flex-col lg:grid lg:h-[calc(100dvh-53px)] lg:grid-cols-[1fr_320px]">
                {/* CENTER: canvas */}
                <main className="overflow-auto bg-background bg-[radial-gradient(circle_at_1px_1px,theme(colors.border)_1px,transparent_0)] bg-[length:22px_22px] p-4 sm:p-6 lg:p-8">
                    <div className="pointer-events-none sticky top-0 z-10 flex justify-center py-2">
                        <button onClick={() => setPickerOpen(true)} className="pointer-events-auto inline-flex items-center gap-2 rounded bg-signal px-4 py-2.5 text-sm font-semibold text-signal-ink shadow-panel hover:bg-signal/90">
                            <span className="text-base leading-none">+</span> Add step
                        </button>
                    </div>
                    <div className="mx-auto flex w-full max-w-[340px] flex-col items-center">
                        <div className="mb-2 w-full rounded border border-border bg-surface p-3">
                            <div className="mb-2 text-[11px] font-bold uppercase tracking-wide text-text-secondary">Targets</div>

                            {/* Read-only. Both halves of the selector are edited in
                                EditWorkflowModal so there is one place to change what
                                a workflow touches; this panel only reports the result. */}
                            <p className="mb-2 text-xs text-text-secondary">
                                {wf.target_server_ids.length} named{" "}
                                <button type="button" onClick={() => setEditWorkflowOpen(true)} className="text-signal hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-signal">
                                    Edit
                                </button>
                            </p>

                            <div className="flex flex-wrap gap-1.5">
                                {Object.entries(targetTags).map(([k, v]) => (
                                    <span key={k} className="rounded-sm border border-border bg-surface-2 px-1.5 py-0.5 font-mono text-[11px]">
                                        <span className="text-text-tertiary">{k}</span>
                                        <span className="text-text-tertiary">:</span>
                                        <span className="text-text-secondary">{v}</span>
                                    </span>
                                ))}
                                {Object.keys(targetTags).length === 0 && <p className="text-xs text-text-secondary">No tag selector — only the named servers will run.</p>}
                            </div>

                            <p className="mt-3 font-mono text-xs text-text-secondary" title={matched.map((s) => s.hostname).join("\n")}>
                                Runs on {matched.length} {matched.length === 1 ? "server" : "servers"}
                            </p>
                            {matched.length === 0 && <p className="text-xs text-danger">This workflow matches no servers and cannot run.</p>}
                        </div>
                        <DropZone pos={0} />
                        {sortedSteps.map((ref, i) => {
                            const lib = libById(ref.step_id);
                            const outs = upstreamOutputsFor(i);
                            const script = ref.inline?.script ?? ref.overrides?.script ?? lib?.script ?? "";
                            const wfIdx = wf.steps.indexOf(ref);
                            const isSelected = selected === i;
                            return (
                                <div key={wfIdx} className="w-full">
                                    {i > 0 && (
                                        <div className="flex flex-col items-center py-1">
                                            <div className="h-[13px] w-0.5 bg-border" />
                                            {outs.length > 0 && (
                                                <div className="flex w-fit max-w-[300px] flex-wrap items-center justify-center gap-1 rounded-full border border-dashed border-signal/55 bg-surface px-3 py-1">
                                                    <span className="text-[10px] uppercase text-text-secondary">passes</span>
                                                    {outs.map((o) => (
                                                        <span key={o} className="rounded bg-signal px-2 py-0.5 font-mono text-[11px] text-signal-ink">
                                                            {o}
                                                        </span>
                                                    ))}
                                                </div>
                                            )}
                                            <div className="h-[13px] w-0.5 bg-border" />
                                        </div>
                                    )}
                                    <div
                                        draggable
                                        onDragStart={(e) => {
                                            e.dataTransfer.setData("text/plain", JSON.stringify({ kind: "move", from: i }));
                                        }}
                                        onClick={() => setSelected(i)}
                                        className={`w-full cursor-pointer rounded border bg-surface p-3 ${isSelected ? "border-signal ring-2 ring-signal/40" : "border-border"}`}
                                    >
                                        <div className="mb-2 flex items-center gap-2">
                                            <span className="grid h-5 w-5 place-items-center rounded border border-border font-mono text-[10px] text-text-secondary">{i + 1}</span>
                                            <span className="text-sm font-medium text-text-primary">{ref.inline?.name ?? lib?.name ?? ref.step_id}</span>
                                            {ref.inline && <ShellBadge interpreter={ref.inline.interpreter} />}
                                            {lib && !ref.inline && <ShellBadge interpreter={lib.interpreter} />}
                                            {ref.inline && <AdhocBadge />}
                                        </div>
                                        <pre className="max-h-16 overflow-hidden text-ellipsis whitespace-pre-wrap rounded border border-border bg-surface-2 p-2 font-mono text-xs text-text-secondary">{script.slice(0, 200)}</pre>
                                    </div>
                                    <DropZone pos={i + 1} />
                                </div>
                            );
                        })}
                        {sortedSteps.length === 0 && (
                            <button onDragOver={(e) => e.preventDefault()} onDrop={(e) => handleDrop(e, 0)} onClick={() => setPickerOpen(true)} className="mt-2 w-full rounded border border-dashed border-border bg-surface py-6 text-sm text-text-secondary hover:border-signal/50 hover:text-text-primary">
                                + Add your first step
                            </button>
                        )}
                    </div>
                </main>

                {/* RIGHT: inspector */}
                <aside className={`overflow-auto border-border bg-surface p-4 lg:block lg:border-l ${selected === null || !selectedRef ? "hidden" : "block max-lg:border-t max-lg:max-h-[60dvh]"}`}>
                    {selected === null || !selectedRef ? (
                        <p className="text-sm text-text-secondary">Select a step to configure it.</p>
                    ) : (
                        <div className="space-y-4">
                            <div>
                                <div className="mb-1 text-[11px] font-bold uppercase tracking-wide text-text-secondary">Step {selected + 1} · Inspector</div>
                                <div className="flex items-center gap-2">
                                    {selectedRef.inline && <ShellBadge interpreter={selectedRef.inline.interpreter} />}
                                    {selectedLib && !selectedRef.inline && <ShellBadge interpreter={selectedLib.interpreter} />}
                                    {selectedRef.inline && <AdhocBadge />}
                                    <h2 className="text-sm font-bold text-text-primary">{selectedRef.inline?.name ?? selectedLib?.name ?? selectedRef.step_id}</h2>
                                </div>
                            </div>

                            {selectedRef.inline ? (
                                <div className="space-y-4 border-b border-border pb-4">
                                    <div>
                                        <label className="mb-1 block text-xs uppercase text-text-secondary">Name</label>
                                        <input className={inputClass} value={selectedRef.inline.name} onChange={(e) => updateInline(selectedIdxInWf, { name: e.target.value })} />
                                    </div>
                                    <div>
                                        <label className="mb-1 block text-xs uppercase text-text-secondary">Interpreter</label>
                                        <select
                                            className={inputClass}
                                            value={selectedRef.inline.interpreter}
                                            onChange={(e) =>
                                                updateInline(selectedIdxInWf, {
                                                    interpreter: e.target.value as WorkflowStep["interpreter"],
                                                })
                                            }
                                        >
                                            <option value="bash">bash</option>
                                            <option value="powershell">powershell</option>
                                        </select>
                                    </div>
                                    <div>
                                        <label className="mb-1 block text-xs uppercase text-text-secondary">Command</label>
                                        <textarea className={`${inputClass} h-32 font-mono text-xs`} value={selectedRef.inline.script} onChange={(e) => updateInline(selectedIdxInWf, { script: e.target.value })} />
                                        <p className="mt-1 text-xs text-text-secondary">
                                            Write <code className="text-signal">KEY=value</code> to <code className="text-signal">$WORKFLOW_ENV</code> to expose it to later steps. Outputs are derived automatically on save.
                                        </p>
                                    </div>
                                </div>
                            ) : (
                                <div className="border-b border-border pb-4">
                                    <label className="mb-1 block text-xs uppercase text-text-secondary">Command</label>
                                    <textarea
                                        className={`${inputClass} h-32 font-mono text-xs ${selectedLib?.source === "default" ? "cursor-not-allowed opacity-70" : ""}`}
                                        readOnly={selectedLib?.source === "default"}
                                        value={selectedRef.overrides?.script ?? selectedLib?.script ?? ""}
                                        onChange={(e) =>
                                            updateRef(selectedIdxInWf, {
                                                overrides: { ...selectedRef.overrides, script: e.target.value },
                                            })
                                        }
                                    />
                                    <p className="mt-1 text-xs text-text-secondary">
                                        {selectedLib?.source === "default" ? (
                                            <>This step ships with Vantage, so its script is fixed. Set its inputs below, or duplicate it into a shared step to change the script.</>
                                        ) : (
                                            <>
                                                Write <code className="text-signal">KEY=value</code> to <code className="text-signal">$WORKFLOW_ENV</code> to expose it to later steps.
                                            </>
                                        )}
                                    </p>
                                </div>
                            )}

                            {(selectedRef.inline?.declared_inputs ?? selectedLib?.declared_inputs ?? []).length > 0 && (
                                <div className="border-b border-border pb-4">
                                    <label className="mb-2 block text-xs uppercase text-text-secondary">Inputs</label>
                                    <div className="space-y-2">
                                        {(selectedRef.inline?.declared_inputs ?? selectedLib?.declared_inputs ?? []).map((param) => (
                                            <div key={param.name}>
                                                <div className="mb-1 font-mono text-xs text-text-primary">{param.name}</div>
                                                {param.description && <div className="mb-1 text-[11px] text-text-secondary">{param.description}</div>}
                                                <input
                                                    className={inputClass}
                                                    placeholder={param.default}
                                                    value={selectedRef.inputs?.[param.name] ?? ""}
                                                    onChange={(e) =>
                                                        updateRef(selectedIdxInWf, {
                                                            inputs: { ...selectedRef.inputs, [param.name]: e.target.value },
                                                        })
                                                    }
                                                />
                                            </div>
                                        ))}
                                    </div>
                                </div>
                            )}

                            <div className="border-b border-border pb-4">
                                <label className="mb-2 block text-xs uppercase text-text-secondary">Inputs · from upstream</label>
                                <div className="flex flex-wrap gap-1">
                                    {upstreamOutputsFor(selected).length === 0 && <p className="text-xs text-text-secondary">No upstream outputs.</p>}
                                    {upstreamOutputsFor(selected).map((o) => (
                                        <span key={o} className="flex items-center gap-1 rounded bg-surface-2 border border-border px-2 py-0.5 font-mono text-[11px] text-text-primary">
                                            <span className="text-[9px] uppercase text-text-secondary">in</span>
                                            {o}
                                        </span>
                                    ))}
                                </div>
                            </div>

                            <div className="border-b border-border pb-4">
                                <label className="mb-2 block text-xs uppercase text-text-secondary">Outputs · to $WORKFLOW_ENV</label>
                                <div className="flex flex-wrap gap-1">
                                    {(selectedRef.inline?.declared_outputs ?? selectedLib?.declared_outputs ?? []).length === 0 && <p className="text-xs text-text-secondary">No declared outputs.</p>}
                                    {(selectedRef.inline?.declared_outputs ?? selectedLib?.declared_outputs ?? []).map((o) => (
                                        <span key={o} className="flex items-center gap-1 rounded bg-signal px-2 py-0.5 font-mono text-[11px] text-signal-ink">
                                            <span className="text-[9px] uppercase">out</span>
                                            {o}
                                        </span>
                                    ))}
                                </div>
                            </div>

                            <div className="border-b border-border pb-4">
                                <label className="mb-2 block text-xs uppercase text-text-secondary">Secret refs</label>
                                <div className="max-h-56 space-y-2 overflow-auto rounded-lg border border-border p-2">
                                    {secretGroups?.map((g) => (
                                        <div key={g.group}>
                                            <div className="font-mono text-[11px] font-semibold text-text-secondary">{g.group}</div>
                                            {(groupKeys[g.group] ?? []).map((key) => {
                                                const ref = `${g.group}/${key}`;
                                                const checked = (selectedRef.inline ? (selectedRef.inline.secret_refs ?? []) : (selectedRef.overrides?.secret_refs ?? [])).includes(ref);
                                                return (
                                                    <label key={ref} className="ml-2 flex cursor-pointer items-center gap-2 text-xs text-text-primary">
                                                        <input type="checkbox" className="accent-signal" checked={checked} onChange={() => toggleSecretRef(ref)} />
                                                        <span className="font-mono">{key}</span>
                                                    </label>
                                                );
                                            })}
                                            {(groupKeys[g.group] ?? []).length === 0 && <p className="ml-2 text-[11px] text-text-secondary">No keys.</p>}
                                        </div>
                                    ))}
                                    {secretGroups && secretGroups.length === 0 && <p className="text-xs text-text-secondary">No secret groups yet.</p>}
                                </div>
                            </div>

                            <div className="border-b border-border pb-4">
                                <label className="mb-1 block text-xs uppercase text-text-secondary">On failure</label>
                                <select
                                    className={inputClass}
                                    value={selectedRef.on_failure}
                                    onChange={(e) =>
                                        updateRef(selectedIdxInWf, {
                                            on_failure: e.target.value as WorkflowStepRef["on_failure"],
                                        })
                                    }
                                >
                                    <option value="stop">Stop workflow</option>
                                    <option value="continue">Continue</option>
                                    <option value="retry">Retry</option>
                                </select>
                                {selectedRef.on_failure === "retry" && (
                                    <div className="mt-2">
                                        <label className="mb-1 block text-xs uppercase text-text-secondary">Max retries</label>
                                        <input type="number" min={1} className={inputClass} value={selectedRef.max_retries} onChange={(e) => updateRef(selectedIdxInWf, { max_retries: parseInt(e.target.value || "0", 10) })} />
                                    </div>
                                )}
                            </div>

                            <Button variant="danger" size="sm" onClick={() => removeStep(selectedIdxInWf)}>
                                Remove from workflow
                            </Button>
                        </div>
                    )}
                </aside>
            </div>

            <EditWorkflowModal
                open={editWorkflowOpen}
                workflow={wf}
                onSaved={(w) => {
                    // Snapshot first: the modal has just persisted name, targets
                    // and tags, so the debounced autosave must not re-send them.
                    savedSnapshotRef.current = snapshotOf(w);
                    setWf(w);
                }}
                onClose={() => setEditWorkflowOpen(false)}
            />
            <StepPickerModal
                open={pickerOpen}
                onClose={() => setPickerOpen(false)}
                onSelect={(stepId) => insertLibStep(stepId, sortedSteps.length)}
                onAddAdhoc={() => {
                    addAdhocStep();
                    setPickerOpen(false);
                }}
                onImportAdhoc={async (file) => {
                    setPickerOpen(false);
                    setImportingInline(true);
                    setError(null);
                    try {
                        const doc = JSON.parse(await file.text());
                        const step = await api.parseStep(doc);
                        appendRef({ inline: step, order: wf.steps.length, on_failure: "stop", max_retries: 0 });
                    } catch (err) {
                        setError((err as Error).message);
                    } finally {
                        setImportingInline(false);
                    }
                }}
            />
        </>
    );
}
