import { useState, useEffect } from "react";
import type {
ContractRepository,
RepositorySourceType,
RepositoryStatus,
DaemonDirectory,
RepositoryHistoryEntry,
} from "../../lib/api";
import { getDaemonDirectories, getRepositorySuggestions } from "../../lib/api";
import { DirectoryInput } from "../mesh/DirectoryInput";
interface RepositoryPanelProps {
repositories: ContractRepository[];
onAddRemote: (name: string, url: string, isPrimary: boolean) => void;
onAddLocal: (name: string, path: string, isPrimary: boolean) => void;
onCreateManaged: (name: string, isPrimary: boolean) => void;
onDelete: (repoId: string) => void;
onSetPrimary: (repoId: string) => void;
}
type AddMode = "remote" | "local" | "managed" | null;
const sourceTypeLabels: Record<RepositorySourceType, string> = {
remote: "Remote",
local: "Local",
managed: "Managed",
};
const sourceTypeIcons: Record<RepositorySourceType, string> = {
remote: "GH",
local: "FS",
managed: "MK",
};
const statusColors: Record<RepositoryStatus, string> = {
ready: "text-green-400",
pending: "text-yellow-400",
creating: "text-cyan-400",
failed: "text-red-400",
};
export function RepositoryPanel({
repositories,
onAddRemote,
onAddLocal,
onCreateManaged,
onDelete,
onSetPrimary,
}: RepositoryPanelProps) {
const [addMode, setAddMode] = useState<AddMode>(null);
const [name, setName] = useState("");
const [url, setUrl] = useState("");
const [path, setPath] = useState("");
const [isPrimary, setIsPrimary] = useState(false);
// Daemon directory suggestions for local repositories
const [suggestedDirectories, setSuggestedDirectories] = useState<DaemonDirectory[]>([]);
// Repository history suggestions
const [repoSuggestions, setRepoSuggestions] = useState<RepositoryHistoryEntry[]>([]);
const [showSuggestions, setShowSuggestions] = useState(false);
// Fetch daemon directories when "local" mode is selected
useEffect(() => {
if (addMode === "local") {
getDaemonDirectories()
.then((res) => setSuggestedDirectories(res.directories))
.catch(() => setSuggestedDirectories([]));
}
}, [addMode]);
// Fetch repository suggestions when mode changes to remote or local
useEffect(() => {
if (addMode === "remote" || addMode === "local") {
getRepositorySuggestions(addMode, undefined, 10)
.then((res) => {
setRepoSuggestions(res.entries);
setShowSuggestions(res.entries.length > 0);
})
.catch(() => {
setRepoSuggestions([]);
setShowSuggestions(false);
});
} else {
setRepoSuggestions([]);
setShowSuggestions(false);
}
}, [addMode]);
// Apply a suggestion to the form
const applySuggestion = (suggestion: RepositoryHistoryEntry) => {
setName(suggestion.name);
if (suggestion.repositoryUrl) {
setUrl(suggestion.repositoryUrl);
}
if (suggestion.localPath) {
setPath(suggestion.localPath);
}
setShowSuggestions(false);
};
const handleAdd = () => {
if (!name.trim()) return;
if (addMode === "remote" && url.trim()) {
onAddRemote(name.trim(), url.trim(), isPrimary);
} else if (addMode === "local" && path.trim()) {
onAddLocal(name.trim(), path.trim(), isPrimary);
} else if (addMode === "managed") {
onCreateManaged(name.trim(), isPrimary);
}
// Reset form
setAddMode(null);
setName("");
setUrl("");
setPath("");
setIsPrimary(false);
};
const handleCancel = () => {
setAddMode(null);
setName("");
setUrl("");
setPath("");
setIsPrimary(false);
};
return (
<div className="space-y-4">
{/* Repository list */}
{repositories.length === 0 ? (
<p className="font-mono text-xs text-[#555]">
No repositories configured
</p>
) : (
<div className="space-y-2">
{repositories.map((repo) => (
<div
key={repo.id}
className="flex items-center gap-3 p-3 border border-[rgba(117,170,252,0.2)]"
>
{/* Type icon */}
<span className="font-mono text-[10px] text-[#555] uppercase w-6">
{sourceTypeIcons[repo.sourceType]}
</span>
{/* Name and details */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-mono text-sm text-[#dbe7ff] truncate">
{repo.name}
</span>
{repo.isPrimary && (
<span className="px-1 py-0.5 text-[8px] font-mono uppercase bg-[rgba(117,170,252,0.1)] text-[#75aafc] border border-[rgba(117,170,252,0.3)]">
Primary
</span>
)}
</div>
<div className="font-mono text-[10px] text-[#555] truncate">
{repo.repositoryUrl || repo.localPath || "(pending creation)"}
</div>
</div>
{/* Status */}
<span
className={`font-mono text-[10px] uppercase ${
statusColors[repo.status]
}`}
>
{repo.status}
</span>
{/* Actions */}
<div className="flex items-center gap-1">
{!repo.isPrimary && repo.status === "ready" && (
<button
onClick={() => onSetPrimary(repo.id)}
className="p-1 font-mono text-[10px] text-[#555] hover:text-[#75aafc] transition-colors"
title="Set as primary"
>
*
</button>
)}
<button
onClick={() => onDelete(repo.id)}
className="p-1 font-mono text-[10px] text-[#555] hover:text-red-400 transition-colors"
title="Remove"
>
x
</button>
</div>
</div>
))}
</div>
)}
{/* Add repository form */}
{addMode ? (
<div className="p-3 border border-[rgba(117,170,252,0.3)] space-y-3">
<div className="flex items-center justify-between mb-2">
<span className="font-mono text-xs text-[#75aafc] uppercase">
Add {sourceTypeLabels[addMode]} Repository
</span>
{repoSuggestions.length > 0 && (
<button
onClick={() => setShowSuggestions(!showSuggestions)}
className="font-mono text-[10px] text-[#555] hover:text-[#9bc3ff] transition-colors"
>
{showSuggestions ? "Hide suggestions" : `${repoSuggestions.length} suggestions`}
</button>
)}
</div>
{/* Suggestions dropdown */}
{showSuggestions && repoSuggestions.length > 0 && (
<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}
onClick={() => applySuggestion(suggestion)}
className="w-full text-left px-3 py-2 font-mono text-xs hover:bg-[rgba(117,170,252,0.1)] transition-colors 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]">
{suggestion.useCount}×
</span>
</div>
<div className="text-[10px] text-[#556677] truncate">
{suggestion.repositoryUrl || suggestion.localPath}
</div>
</button>
))}
</div>
)}
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Repository name"
className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#75aafc]"
/>
{addMode === "remote" && (
<input
type="text"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://github.com/owner/repo"
className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-xs focus:outline-none focus:border-[#75aafc]"
/>
)}
{addMode === "local" && (
<DirectoryInput
value={path}
onChange={setPath}
suggestions={suggestedDirectories}
placeholder="/path/to/repository"
/>
)}
{addMode === "managed" && (
<p className="font-mono text-xs text-[#555]">
Makima will create this repository via the daemon.
</p>
)}
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={isPrimary}
onChange={(e) => setIsPrimary(e.target.checked)}
className="w-3 h-3"
/>
<span className="font-mono text-xs text-[#9bc3ff]">
Set as primary repository
</span>
</label>
<div className="flex gap-2">
<button
onClick={handleCancel}
className="px-3 py-1.5 font-mono text-xs text-[#555] hover:text-[#9bc3ff] transition-colors"
>
Cancel
</button>
<button
onClick={handleAdd}
disabled={
!name.trim() ||
(addMode === "remote" && !url.trim()) ||
(addMode === "local" && !path.trim())
}
className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Add Repository
</button>
</div>
</div>
) : (
<div className="flex gap-2">
<button
onClick={() => setAddMode("remote")}
className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
>
+ Remote
</button>
<button
onClick={() => setAddMode("local")}
className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
>
+ Local
</button>
<button
onClick={() => setAddMode("managed")}
className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
>
+ Managed
</button>
</div>
)}
</div>
);
}