import { useState, useEffect, useRef } from "react";
import { useNavigate } from "react-router";
import type {
DirectiveWithChains,
DirectiveStatus,
ChainWithSteps,
ChainStep,
ContractPhase,
} from "../../lib/api";
import { getDirective } from "../../lib/api";
import { PhaseProgressBarCompact } from "../contracts/PhaseProgressBar";
import { StepDiagram } from "./StepDiagram";
import { DirectiveContractsTab } from "./DirectiveContractsTab";
interface DirectiveDetailProps {
directive: DirectiveWithChains;
onBack: () => void;
onDelete?: (id: string) => void;
onStart?: (id: string) => void;
onRefresh?: (updated: DirectiveWithChains) => void;
}
type Tab = "overview" | "chain" | "contracts";
const statusColors: Record<DirectiveStatus, string> = {
draft: "text-[#888]",
planning: "text-yellow-400",
active: "text-green-400",
paused: "text-orange-400",
completed: "text-blue-400",
archived: "text-[#555]",
failed: "text-red-400",
};
const stepStatusColors: Record<string, string> = {
pending: "text-[#888]",
running: "text-yellow-400",
passed: "text-green-400",
failed: "text-red-400",
};
const stepStatusIcons: Record<string, string> = {
pending: "\u25CB", // ○
running: "\u25D4", // ◔
passed: "\u25CF", // ●
failed: "\u2715", // ✕
};
function StepRow({ step }: { step: ChainStep }) {
const navigate = useNavigate();
const color = stepStatusColors[step.status] || "text-[#888]";
const icon = stepStatusIcons[step.status] || "\u25CB";
const summary = step.contractSummary;
return (
<div className="flex items-start gap-2 py-1.5 px-2 hover:bg-[rgba(117,170,252,0.05)]">
<span className={`font-mono text-[11px] ${color} mt-px`}>{icon}</span>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-mono text-[11px] text-[#dbe7ff] truncate">
{step.name}
</span>
<span className={`font-mono text-[9px] uppercase ${color}`}>
{step.status}
</span>
</div>
{summary && (
<div className="flex items-center gap-2 mt-0.5">
<PhaseProgressBarCompact
currentPhase={summary.phase as ContractPhase}
/>
<span className="font-mono text-[9px] text-[#7788aa]">
{summary.tasksDone}/{summary.taskCount} tasks
</span>
{step.contractId && (
<button
onClick={(e) => {
e.stopPropagation();
navigate(`/contracts/${step.contractId}`);
}}
className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors"
>
contract →
</button>
)}
</div>
)}
{!summary && step.contractId && (
<button
onClick={() => navigate(`/contracts/${step.contractId}`)}
className="font-mono text-[9px] text-[#75aafc] hover:text-white transition-colors mt-0.5"
>
contract →
</button>
)}
{step.description && (
<p className="font-mono text-[10px] text-[#7788aa] truncate mt-0.5">
{step.description}
</p>
)}
</div>
</div>
);
}
function ChainCard({ chainWithSteps }: { chainWithSteps: ChainWithSteps }) {
const chain = chainWithSteps;
const steps = chainWithSteps.steps || [];
return (
<div className="border border-dashed border-[rgba(117,170,252,0.25)] bg-[rgba(117,170,252,0.03)]">
<div className="p-3">
<div className="flex items-center justify-between mb-1">
<span className="font-mono text-xs text-[#dbe7ff]">
{chain.name}
</span>
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
gen {chain.generation} · {chain.status}
</span>
</div>
{chain.description && (
<p className="font-mono text-[11px] text-[#7788aa] mb-1">
{chain.description}
</p>
)}
<div className="flex gap-3 font-mono text-[10px] text-[#7788aa]">
<span>
{chain.completedSteps}/{chain.totalSteps} steps
</span>
{chain.failedSteps > 0 && (
<span className="text-red-400">{chain.failedSteps} failed</span>
)}
{chain.currentConfidence != null && (
<span>
confidence: {(chain.currentConfidence * 100).toFixed(0)}%
</span>
)}
</div>
</div>
{steps.length > 0 && (
<div className="border-t border-dashed border-[rgba(117,170,252,0.15)]">
{steps.map((step) => (
<StepRow key={step.id} step={step} />
))}
</div>
)}
</div>
);
}
function JsonSection({
label,
data,
}: {
label: string;
data: unknown[] | unknown;
}) {
const items = Array.isArray(data) ? data : [];
if (items.length === 0) return null;
return (
<div>
<h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
{label}
</h4>
<div className="font-mono text-xs text-[#9bb8d8] bg-[rgba(0,0,0,0.2)] p-2 max-h-32 overflow-y-auto">
{items.map((item, i) => (
<div key={i} className="mb-0.5">
{typeof item === "string" ? item : JSON.stringify(item)}
</div>
))}
</div>
</div>
);
}
export function DirectiveDetail({
directive,
onBack,
onDelete,
onStart,
onRefresh,
}: DirectiveDetailProps) {
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<Tab>("overview");
// Auto-poll when directive is in an active state
const isLive =
directive.status === "planning" || directive.status === "active";
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
if (!isLive) {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
intervalRef.current = setInterval(async () => {
try {
const updated = await getDirective(directive.id);
if (updated && onRefresh) {
onRefresh(updated);
}
} catch {
// Ignore poll errors
}
}, 5000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
}, [isLive, directive.id, onRefresh]);
// Count total steps and completed steps across all chains
const totalSteps = directive.chains.reduce(
(sum, c) => sum + c.totalSteps,
0
);
const completedSteps = directive.chains.reduce(
(sum, c) => sum + c.completedSteps,
0
);
// Count contracts
const contractCount =
(directive.orchestratorContractSummary ? 1 : 0) +
directive.chains.reduce(
(sum, c) =>
sum + c.steps.filter((s) => s.contractSummary != null).length,
0
);
const tabs: { key: Tab; label: string; count?: number }[] = [
{ key: "overview", label: "Overview" },
{ key: "chain", label: "Chain", count: totalSteps },
{ key: "contracts", label: "Contracts", count: contractCount },
];
return (
<div className="panel h-full flex flex-col">
{/* Header */}
<div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]">
<div className="flex items-center justify-between mb-3">
<button
onClick={onBack}
className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
>
← Back to list
</button>
<div className="flex items-center gap-2">
{onStart && directive.status === "draft" && (
<button
onClick={() => onStart(directive.id)}
className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
>
Start
</button>
)}
{onDelete && (
<button
onClick={() => onDelete(directive.id)}
className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
>
Delete
</button>
)}
</div>
</div>
<div className="flex items-center gap-3 mb-2">
<h2 className="font-mono text-lg text-[#dbe7ff]">
{directive.title}
</h2>
<span
className={`font-mono text-xs uppercase ${
statusColors[directive.status as DirectiveStatus] || "text-[#888]"
}`}
>
{directive.status}
</span>
{isLive && (
<span className="font-mono text-[9px] text-yellow-400/60 animate-pulse">
polling
</span>
)}
<span className="font-mono text-[10px] text-[#7788aa]">
v{directive.version}
</span>
</div>
</div>
{/* Tabs */}
<div className="flex border-b border-[rgba(117,170,252,0.2)]">
{tabs.map((tab) => (
<button
key={tab.key}
onClick={() => setActiveTab(tab.key)}
className={`
px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors
${
activeTab === tab.key
? "text-[#dbe7ff] border-b-2 border-[#75aafc]"
: "text-[#555] hover:text-[#9bc3ff]"
}
`}
>
{tab.label}
{tab.count != null && tab.count > 0 && (
<span className="ml-1 text-[10px] text-[#7788aa]">
({tab.count})
</span>
)}
</button>
))}
</div>
{/* Tab content */}
<div className="flex-1 overflow-y-auto p-4">
{activeTab === "overview" && (
<div className="space-y-4">
{/* Orchestrator contract link */}
{directive.orchestratorContractId && (
<div className="flex items-center gap-2 p-2 border border-dashed border-[rgba(117,170,252,0.2)] bg-[rgba(117,170,252,0.03)]">
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
Planning Contract
</span>
{directive.orchestratorContractSummary && (
<PhaseProgressBarCompact
currentPhase={
directive.orchestratorContractSummary
.phase as ContractPhase
}
/>
)}
<button
onClick={() =>
navigate(
`/contracts/${directive.orchestratorContractId}`
)
}
className="font-mono text-[11px] text-[#75aafc] hover:text-white transition-colors"
>
{directive.orchestratorContractSummary?.name ||
directive.orchestratorContractId.slice(0, 8) + "..."}{" "}
→
</button>
{directive.status === "planning" && (
<span className="font-mono text-[9px] text-yellow-400 animate-pulse">
planning in progress
</span>
)}
</div>
)}
{/* Goal */}
<div>
<h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
Goal
</h4>
<p className="font-mono text-xs text-[#9bb8d8] whitespace-pre-wrap">
{directive.goal}
</p>
</div>
{/* Config grid */}
<div className="grid grid-cols-3 gap-2">
<div>
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
Autonomy
</span>
<div className="font-mono text-xs text-[#dbe7ff]">
{directive.autonomyLevel}
</div>
</div>
<div>
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
Chains
</span>
<div className="font-mono text-xs text-[#dbe7ff]">
{directive.chainGenerationCount} generated
</div>
</div>
<div>
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
Cost
</span>
<div className="font-mono text-xs text-[#dbe7ff]">
${directive.totalCostUsd.toFixed(2)}
</div>
</div>
{directive.repositoryUrl && (
<div className="col-span-3">
<span className="font-mono text-[10px] text-[#7788aa] uppercase">
Repository
</span>
<div className="font-mono text-xs text-[#dbe7ff] truncate">
{directive.repositoryUrl}
</div>
</div>
)}
</div>
{/* Stat cards */}
<div className="grid grid-cols-3 gap-2">
<div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
<div className="font-mono text-lg text-[#dbe7ff]">
{totalSteps}
</div>
<div className="font-mono text-[9px] text-[#7788aa] uppercase">
Total Steps
</div>
</div>
<div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
<div className="font-mono text-lg text-green-400">
{completedSteps}
</div>
<div className="font-mono text-[9px] text-[#7788aa] uppercase">
Completed
</div>
</div>
<div className="border border-dashed border-[rgba(117,170,252,0.2)] p-2 text-center">
<div className="font-mono text-lg text-[#dbe7ff]">
${directive.totalCostUsd.toFixed(2)}
</div>
<div className="font-mono text-[9px] text-[#7788aa] uppercase">
Cost
</div>
</div>
</div>
{/* Structured sections */}
<JsonSection label="Requirements" data={directive.requirements} />
<JsonSection
label="Acceptance Criteria"
data={directive.acceptanceCriteria}
/>
<JsonSection label="Constraints" data={directive.constraints} />
<JsonSection
label="External Dependencies"
data={directive.externalDependencies}
/>
{/* Metadata */}
<div>
<h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-1">
Metadata
</h4>
<div className="grid grid-cols-2 gap-1 font-mono text-[10px]">
<span className="text-[#7788aa]">Created</span>
<span className="text-[#9bb8d8]">
{new Date(directive.createdAt).toLocaleString()}
</span>
<span className="text-[#7788aa]">Updated</span>
<span className="text-[#9bb8d8]">
{new Date(directive.updatedAt).toLocaleString()}
</span>
{directive.startedAt && (
<>
<span className="text-[#7788aa]">Started</span>
<span className="text-[#9bb8d8]">
{new Date(directive.startedAt).toLocaleString()}
</span>
</>
)}
{directive.completedAt && (
<>
<span className="text-[#7788aa]">Completed</span>
<span className="text-[#9bb8d8]">
{new Date(directive.completedAt).toLocaleString()}
</span>
</>
)}
<span className="text-[#7788aa]">Version</span>
<span className="text-[#9bb8d8]">{directive.version}</span>
</div>
</div>
</div>
)}
{activeTab === "chain" && (
<div className="space-y-4">
{/* Step diagram */}
{directive.chains.length > 0 && (
<div>
<h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2">
Step Dependencies
</h4>
<StepDiagram
steps={directive.chains.flatMap((c) => c.steps)}
/>
</div>
)}
{/* Chain cards */}
<div>
<h4 className="font-mono text-[10px] text-[#75aafc] uppercase tracking-wider mb-2">
Chains ({directive.chains.length})
</h4>
{directive.chains.length === 0 ? (
<p className="font-mono text-xs text-[#7788aa]">
{directive.status === "planning"
? "Planning in progress... chains will appear when the planner completes."
: directive.status === "draft"
? "No chains yet. Start the directive to begin planning."
: "No chains created for this directive."}
</p>
) : (
<div className="space-y-2">
{directive.chains.map((cws) => (
<ChainCard key={cws.id} chainWithSteps={cws} />
))}
</div>
)}
</div>
</div>
)}
{activeTab === "contracts" && (
<DirectiveContractsTab directive={directive} />
)}
</div>
</div>
);
}