import { useState, useEffect, useCallback } from "react";
import { useParams, useNavigate } from "react-router";
import { Masthead } from "../components/Masthead";
import { DirectiveList } from "../components/directives/DirectiveList";
import { DirectiveDetail } from "../components/directives/DirectiveDetail";
import { useDirectives, useDirective } from "../hooks/useDirectives";
import { useDogs } from "../hooks/useDogs";
import { useUserSettings } from "../hooks/useUserSettings";
import { useAuth } from "../contexts/AuthContext";
import DocumentDirectivesPage from "./document-directives";
import { getRepositorySuggestions, startDirective, pauseDirective, updateDirective, type RepositoryHistoryEntry, type DirectiveSummary } from "../lib/api";
/**
* Top-level /directives route. Gates between the legacy tabular UI and the
* Document Mode (POC) UI based on the user's settings flag.
*
* Both code paths support /directives/:id deep links — the param is read by
* each branch independently via useParams.
*/
export default function DirectivesPage() {
const { settings, loading: settingsLoading } = useUserSettings();
// While settings are loading for the very first time, render nothing inside
// a Masthead-wrapped shell so we don't briefly flash the legacy UI just to
// swap to document mode a moment later.
if (settingsLoading && !settings) {
return (
<div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
<Masthead showNav />
<main className="flex-1 flex items-center justify-center">
<p className="text-[#7788aa] font-mono text-sm">Loading...</p>
</main>
</div>
);
}
if (settings?.documentModeEnabled) {
return <DocumentDirectivesPage />;
}
return <LegacyDirectivesPage />;
}
function LegacyDirectivesPage() {
const { isAuthenticated, isAuthConfigured, isLoading: authLoading } = useAuth();
const navigate = useNavigate();
const { id: selectedId } = useParams<{ id: string }>();
const { directives, loading: listLoading, create, remove, refresh: refreshList } = useDirectives();
const { directive, refresh: refreshDetail, update, start, pause, advance, completeStep, failStep, skipStep, updateGoal, cleanup, pickUpOrders, createPR } = useDirective(selectedId);
const { dogs, loading: dogsLoading, create: createDog, update: updateDog, remove: removeDog, pickUpOrders: pickUpDogOrders } = useDogs(selectedId);
const [showCreate, setShowCreate] = useState(false);
const [newTitle, setNewTitle] = useState("");
const [newGoal, setNewGoal] = useState("");
const [newRepoUrl, setNewRepoUrl] = useState("");
const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
const [showRepoSuggestions, setShowRepoSuggestions] = useState(false);
// Fetch repository suggestions when create form opens
useEffect(() => {
if (showCreate) {
getRepositorySuggestions("remote", undefined, 10)
.then((res) => {
setRepoSuggestions(res.entries);
setShowRepoSuggestions(res.entries.length > 0);
})
.catch(() => {
setRepoSuggestions([]);
setShowRepoSuggestions(false);
});
} else {
setRepoSuggestions([]);
setShowRepoSuggestions(false);
}
}, [showCreate]);
const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => {
if (suggestion.repositoryUrl) {
setNewRepoUrl(suggestion.repositoryUrl);
}
if (!newTitle.trim() && suggestion.name) {
setNewTitle(suggestion.name);
}
setShowRepoSuggestions(false);
}, [newTitle]);
useEffect(() => {
if (!authLoading && isAuthConfigured && !isAuthenticated) {
navigate("/login");
}
}, [authLoading, isAuthConfigured, isAuthenticated, navigate]);
if (authLoading) {
return (
<div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
<Masthead showNav />
<main className="flex-1 flex items-center justify-center">
<p className="text-[#7788aa] font-mono text-sm">Loading...</p>
</main>
</div>
);
}
const handleContextStart = async (directive: DirectiveSummary) => {
try {
await startDirective(directive.id);
await refreshList();
} catch (e) {
console.error("Failed to start directive:", e);
}
};
const handleContextPause = async (directive: DirectiveSummary) => {
try {
await pauseDirective(directive.id);
await refreshList();
} catch (e) {
console.error("Failed to pause directive:", e);
}
};
const handleContextArchive = async (directive: DirectiveSummary) => {
try {
await updateDirective(directive.id, { status: "archived" });
await refreshList();
} catch (e) {
console.error("Failed to archive directive:", e);
}
};
const handleContextDelete = async (directive: DirectiveSummary) => {
if (!window.confirm("Delete this directive?")) return;
try {
await remove(directive.id);
if (directive.id === selectedId) navigate("/directives");
} catch (e) {
console.error("Failed to delete:", e);
}
};
const handleContextGoToPR = (directive: DirectiveSummary) => {
if (directive.prUrl) window.open(directive.prUrl, "_blank");
};
const handleCreate = async () => {
if (!newTitle.trim() || !newGoal.trim()) return;
try {
const d = await create({
title: newTitle.trim(),
goal: newGoal.trim(),
repositoryUrl: newRepoUrl.trim() || undefined,
});
setShowCreate(false);
setNewTitle("");
setNewGoal("");
setNewRepoUrl("");
navigate(`/directives/${d.id}`);
} catch (e) {
console.error("Failed to create directive:", e);
}
};
const handleDelete = async () => {
if (!selectedId) return;
if (!window.confirm("Delete this directive?")) return;
try {
await remove(selectedId);
navigate("/directives");
} catch (e) {
console.error("Failed to delete:", e);
}
};
return (
<div className="relative z-10 min-h-screen flex flex-col bg-[#0a1628]">
<Masthead showNav />
<main className="flex-1 flex overflow-hidden" style={{ height: "calc(100vh - 80px)" }}>
{/* Left: List */}
<div className="w-[280px] shrink-0 border-r border-dashed border-[rgba(117,170,252,0.2)] overflow-hidden flex flex-col">
<DirectiveList
directives={directives}
selectedId={selectedId ?? null}
onSelect={(id) => navigate(`/directives/${id}`)}
onCreate={() => setShowCreate(true)}
onStart={handleContextStart}
onPause={handleContextPause}
onArchive={handleContextArchive}
onDelete={handleContextDelete}
onGoToPR={handleContextGoToPR}
/>
</div>
{/* Right: Detail or Create */}
<div className="flex-1 overflow-hidden">
{showCreate ? (
<div className="p-4 max-w-lg">
<h2 className="text-[14px] font-mono text-white font-medium mb-4">
New Directive
</h2>
<div className="flex flex-col gap-3">
<div>
<label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
Title
</label>
<input
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="Project title..."
className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
/>
</div>
<div>
<label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
Goal
</label>
<textarea
value={newGoal}
onChange={(e) => setNewGoal(e.target.value)}
placeholder="What should this directive accomplish?"
rows={4}
className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white resize-y"
/>
</div>
{showRepoSuggestions && repoSuggestions.length > 0 && (
<div>
<label className="block font-mono text-xs text-[#8b949e] uppercase mb-1">
Recent Repositories
</label>
<div className="border border-[rgba(117,170,252,0.2)] bg-[#0a1525] max-h-32 overflow-y-auto">
{repoSuggestions.map((suggestion) => (
<button
key={suggestion.id}
type="button"
onClick={() => applyRepoSuggestion(suggestion)}
className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] border-b border-[rgba(117,170,252,0.1)] last:border-b-0"
>
<div className="flex items-center justify-between">
<span className="text-[#9bc3ff] truncate">{suggestion.name}</span>
<span className="text-[10px] text-[#556677] ml-2">
{suggestion.useCount}×
</span>
</div>
<div className="text-[10px] text-[#556677] truncate">
{suggestion.repositoryUrl}
</div>
</button>
))}
</div>
</div>
)}
<div>
<label className="text-[10px] font-mono text-[#9bc3ff] uppercase tracking-wide block mb-1">
Repository URL (optional)
</label>
<input
value={newRepoUrl}
onChange={(e) => setNewRepoUrl(e.target.value)}
placeholder="https://github.com/..."
className="w-full bg-[#0a1628] border border-[rgba(117,170,252,0.2)] rounded px-2 py-1.5 text-[12px] font-mono text-white"
/>
</div>
<div className="flex gap-2">
<button
type="button"
onClick={handleCreate}
disabled={!newTitle.trim() || !newGoal.trim()}
className="text-[11px] font-mono text-emerald-400 hover:text-emerald-300 border border-emerald-800 rounded px-3 py-1 disabled:opacity-50"
>
Create
</button>
<button
type="button"
onClick={() => setShowCreate(false)}
className="text-[11px] font-mono text-[#7788aa] hover:text-white border border-[#2a3a5a] rounded px-3 py-1"
>
Cancel
</button>
</div>
</div>
</div>
) : selectedId && directive ? (
<DirectiveDetail
directive={directive}
onStart={start}
onPause={pause}
onAdvance={advance}
onCompleteStep={completeStep}
onFailStep={failStep}
onSkipStep={skipStep}
onUpdateGoal={updateGoal}
onUpdate={update}
onDelete={handleDelete}
onRefresh={refreshDetail}
onCleanup={cleanup}
onPickUpOrders={pickUpOrders}
onCreatePR={createPR}
dogs={dogs}
dogsLoading={dogsLoading}
onCreateDog={createDog}
onUpdateDog={updateDog}
onDeleteDog={removeDog}
onPickUpDogOrders={pickUpDogOrders}
/>
) : (
<div className="flex-1 flex items-center justify-center h-full">
<p className="text-[#556677] font-mono text-[12px]">
{listLoading
? "Loading..."
: "Select a directive or create a new one"}
</p>
</div>
)}
</div>
</main>
</div>
);
}