export type SessionStatus =
	| "working"
	| "pr_open"
	| "draft"
	| "ci_failed"
	| "review_pending"
	| "changes_requested"
	| "approved"
	| "mergeable"
	| "merged"
	| "needs_input"
	| "idle"
	| "terminated";

const sessionStatuses = new Set<SessionStatus>([
	"working",
	"pr_open",
	"draft",
	"ci_failed",
	"review_pending",
	"changes_requested",
	"approved",
	"mergeable",
	"merged",
	"needs_input",
	"idle",
	"terminated",
]);

export function toSessionStatus(status?: string, isTerminated = false): SessionStatus {
	if (isTerminated) return "terminated";
	return status && sessionStatuses.has(status as SessionStatus) ? (status as SessionStatus) : "working";
}

export type AgentProvider =
	| "codex"
	| "claude-code"
	| "opencode"
	| "aider"
	| "grok"
	| "droid"
	| "amp"
	| "agy"
	| "crush"
	| "cursor"
	| "qwen"
	| "copilot"
	| "goose"
	| "auggie"
	| "continue"
	| "devin"
	| "cline"
	| "kimi"
	| "kiro"
	| "kilocode"
	| "vibe"
	| "pi"
	| "autohand";

/** A file in a worker's worktree diff (drives the Git review rail). */
export type ChangedFile = {
	path: string;
	additions: number;
	deletions: number;
	staged?: boolean;
};

export type SessionKind = "worker" | "orchestrator";

export type WorkspaceSession = {
	id: string;
	terminalHandleId?: string;
	workspaceId: string;
	workspaceName: string;
	title: string;
	provider: AgentProvider;
	kind?: SessionKind;
	branch: string;
	status: SessionStatus;
	/** ISO timestamp from the daemon — used for relative time in the inspector. */
	createdAt?: string;
	/** ISO timestamp from the daemon. */
	updatedAt: string;
	/** The session's git diff against its base, when known. */
	changedFiles?: ChangedFile[];
	/** Pre-filled commit subject for the Git rail, when known. */
	commitMessage?: string;
	pullRequest?: {
		number: number;
		state: "open" | "draft" | "merged" | "closed";
	};
	/**
	 * Display status as derived by the daemon at read time. Optional override; when
	 * absent it is derived from {@link SessionStatus} via {@link workerDisplayStatus}.
	 */
	displayStatus?: WorkerDisplayStatus;
};

/** Glanceable worker status. Maps 1:1 to the accent colors in DESIGN.md. */
export type WorkerDisplayStatus = "working" | "needs_you" | "mergeable" | "ci_failed" | "done";

export function workerDisplayStatus(session: WorkspaceSession): WorkerDisplayStatus {
	if (session.displayStatus) return session.displayStatus;
	switch (session.status) {
		case "needs_input":
		case "changes_requested":
		case "review_pending":
			return "needs_you";
		case "ci_failed":
			return "ci_failed";
		case "approved":
		case "mergeable":
			return "mergeable";
		case "merged":
		case "terminated":
			return "done";
		default:
			return "working";
	}
}

export function isOrchestratorSession(session: WorkspaceSession): boolean {
	return session.kind === "orchestrator" || session.id.endsWith("-orchestrator");
}

/**
 * The project's LIVE orchestrator, if any. Terminated orchestrator rows stay in
 * the session list (the daemon returns all sessions, ordered by spawn number),
 * so an earlier dead orchestrator must not shadow a live one — its zellij
 * session is deleted and attaching to it dead-ends in an instant
 * "[process exited]". No live orchestrator → undefined, so the topbar offers
 * Spawn instead of navigating to a dead session.
 */
export function findProjectOrchestrator(
	workspaces: WorkspaceSummary[],
	projectId: string,
): WorkspaceSession | undefined {
	const workspace = workspaces.find((w) => w.id === projectId);
	return workspace?.sessions.find((session) => isOrchestratorSession(session) && sessionIsActive(session));
}

export function workerSessions(sessions: WorkspaceSession[]): WorkspaceSession[] {
	return sessions.filter((s) => !isOrchestratorSession(s));
}

export function sessionIsActive(session: WorkspaceSession): boolean {
	return session.status !== "merged" && session.status !== "terminated";
}

export function sessionNeedsAttention(session: WorkspaceSession): boolean {
	return (
		session.status === "needs_input" ||
		session.status === "changes_requested" ||
		session.status === "review_pending" ||
		session.status === "ci_failed"
	);
}

export const workerStatusLabel: Record<WorkerDisplayStatus, string> = {
	working: "working",
	needs_you: "needs you",
	mergeable: "mergeable",
	ci_failed: "ci failed",
	done: "done",
};

/** Whether a status should breathe (alive/working). */
export function workerStatusPulses(status: WorkerDisplayStatus): boolean {
	return status === "working" || status === "needs_you";
}

/**
 * Kanban attention zone, ordered by human-action urgency — ported from
 * agent-orchestrator's getAttentionLevel (packages/web/src/lib/types.ts),
 * collapsed to its default "simple" set and rebound to reverbcode's
 * {@link SessionStatus}. The board groups sessions into these columns so the
 * highest-ROrI work (a one-click merge) sits leftmost.
 */
export type AttentionZone = "merge" | "action" | "pending" | "working" | "done";

/** Columns left→right, most-urgent first. "done" is the archive column. */
export const attentionZoneOrder: AttentionZone[] = ["merge", "action", "pending", "working", "done"];

export const attentionZoneLabel: Record<AttentionZone, string> = {
	merge: "Ready to merge",
	action: "Needs you",
	pending: "Pending",
	working: "Working",
	done: "Done",
};

export function attentionZone(session: WorkspaceSession): AttentionZone {
	switch (session.status) {
		// Terminal — archive.
		case "merged":
		case "terminated":
			return "done";
		// One click to clear — highest ROI, checked first.
		case "approved":
		case "mergeable":
			return "merge";
		// Agent waiting on a human (respond) or a problem to investigate (review);
		// agent-orchestrator collapses these into one "action" zone by default.
		case "needs_input":
		case "ci_failed":
		case "changes_requested":
			return "action";
		// Waiting on an external reviewer / CI — nothing to do right now.
		case "review_pending":
		case "pr_open":
		case "draft":
			return "pending";
		// Agents doing their thing — don't interrupt.
		case "working":
		case "idle":
		default:
			return "working";
	}
}

export type WorkspaceSummary = {
	id: string;
	name: string;
	path: string;
	type?: "main" | "worktree";
	accentColor?: string;
	diff?: {
		additions: number;
		deletions: number;
	};
	pullRequest?: {
		number: number;
		state: "open" | "draft" | "merged" | "closed";
	};
	sessions: WorkspaceSession[];
};

export function toAgentProvider(provider?: string): AgentProvider {
	switch (provider) {
		case "claude-code":
		case "opencode":
		case "aider":
		case "grok":
		case "droid":
		case "amp":
		case "agy":
		case "crush":
		case "cursor":
		case "qwen":
		case "copilot":
		case "goose":
		case "auggie":
		case "continue":
		case "devin":
		case "cline":
		case "kimi":
		case "kiro":
		case "kilocode":
		case "vibe":
		case "pi":
		case "autohand":
			return provider;
		default:
			return "codex";
	}
}
