import type { AgentMessage } from "@earendil-works/pi-agent-core";
import { type AgentObserveAgentSummary, createAgentObserveMessagePreview } from "./agent-observe.js";
import type { HostRequestHandler } from "./kernel/index.js";

export const AGENT_SESSIONS_HOST_PREFIX = "agent.sessions";
export type AgentSessionCapability = "send" | "recent_turns" | "stop" | "restart";
export type AgentSessionDelivery = "steer" | "follow_up";

export type AgentSessionSummary = AgentObserveAgentSummary & {
	capabilities: readonly AgentSessionCapability[];
};

export interface AgentSessionRecentTurnsInput {
	target: string;
	count?: number;
	maxChars?: number;
}

export interface AgentSessionTurnSummary {
	user: string;
	assistant: string;
	toolCalls: number;
	startedAt?: number;
	endedAt?: number;
	messageCount: number;
	truncated: boolean;
}

export interface AgentSessionRecentTurnsResult {
	agent: AgentSessionSummary;
	turns: AgentSessionTurnSummary[];
	count: number;
	maxChars: number;
	truncated: boolean;
}

export interface AgentSessionSendInput {
	target: string;
	message: string;
	delivery: AgentSessionDelivery;
}

export interface AgentSessionSendResult {
	operationId: string;
	status: "accepted" | "queued";
	delivery: AgentSessionDelivery;
	target: {
		activeSessionId: string;
		sessionId: string;
		sessionName?: string;
	};
}

export interface AgentSessionOperationResult {
	operationId: string;
	status: "accepted";
}

export interface AgentSessionController {
	listSessions(): AgentSessionSummary[] | Promise<AgentSessionSummary[]>;
	getSession(target: string): AgentSessionSummary | Promise<AgentSessionSummary>;
	recentTurns(
		input: AgentSessionRecentTurnsInput,
	): AgentSessionRecentTurnsResult | Promise<AgentSessionRecentTurnsResult>;
	send(input: AgentSessionSendInput): AgentSessionSendResult | Promise<AgentSessionSendResult>;
	stop(target: string): AgentSessionOperationResult | Promise<AgentSessionOperationResult>;
	restart(target: string): AgentSessionOperationResult | Promise<AgentSessionOperationResult>;
}

export function createAgentSessionHostHandlers(controller: AgentSessionController): Record<string, HostRequestHandler> {
	return {
		"agent.sessions.list": async () => ({ sessions: await controller.listSessions() }),
		"agent.sessions.get": async (payload: Record<string, unknown> = {}) => ({
			session: await controller.getSession(requireString(payload, "target", "agent.sessions.get target")),
		}),
		"agent.sessions.recent_turns": async (payload: Record<string, unknown> = {}) =>
			(await controller.recentTurns({
				target: requireString(payload, "target", "agent.sessions.recent_turns target"),
				count: normalizeOptionalInteger(payload.count, "agent.sessions.recent_turns count"),
				maxChars: normalizeOptionalInteger(
					payload.max_chars ?? payload.maxChars,
					"agent.sessions.recent_turns max_chars",
				),
			})) as unknown as Record<string, unknown>,
		"agent.sessions.send": async (payload: Record<string, unknown> = {}) =>
			(await controller.send({
				target: requireString(payload, "target", "agent.sessions.send target"),
				message: requireString(payload, "message", "agent.sessions.send message"),
				delivery: normalizeDelivery(payload.delivery),
			})) as unknown as Record<string, unknown>,
		"agent.sessions.stop": async (payload: Record<string, unknown> = {}) =>
			(await controller.stop(requireString(payload, "target", "agent.sessions.stop target"))) as unknown as Record<
				string,
				unknown
			>,
		"agent.sessions.restart": async (payload: Record<string, unknown> = {}) =>
			(await controller.restart(
				requireString(payload, "target", "agent.sessions.restart target"),
			)) as unknown as Record<string, unknown>,
	};
}

export function normalizeAgentSessionTurnCount(count: number | undefined): number {
	return clampInteger(count ?? 3, 1, 20, "agent.sessions.recent_turns count");
}

export function normalizeAgentSessionMaxChars(maxChars: number | undefined): number {
	return clampInteger(maxChars ?? 6_000, 80, 12_000, "agent.sessions.recent_turns max_chars");
}

export function createAgentSessionTurnSummaries(
	messages: AgentMessage[],
	count: number,
	maxChars: number,
): { turns: AgentSessionTurnSummary[]; truncated: boolean } {
	const previews = messages.map((message, index) => createAgentObserveMessagePreview(message, index, maxChars));
	const allTurns: MutableTurn[] = [];
	let current: MutableTurn | undefined;

	for (const preview of previews) {
		if (preview.role === "user" && current?.hasUser) {
			current = undefined;
		}
		if (!current) {
			current = createMutableTurn();
			allTurns.push(current);
		}
		current.messageCount += 1;
		if (current.startedAt === undefined && preview.timestamp !== undefined) current.startedAt = preview.timestamp;
		if (preview.timestamp !== undefined) current.endedAt = preview.timestamp;
		current.truncated ||= preview.truncated;
		if (preview.role === "user") {
			current.hasUser = true;
			const result = appendText(current.user, preview.text, maxChars);
			current.user = result.text;
			current.truncated ||= result.truncated;
		} else if (preview.role === "assistant") {
			const result = appendText(current.assistant, preview.text, maxChars);
			current.assistant = result.text;
			current.truncated ||= result.truncated;
			current.toolCalls += preview.toolCalls?.length ?? 0;
		}
	}

	const selected = allTurns.slice(-count).map(({ hasUser: _hasUser, ...turn }) => turn);
	return {
		turns: selected,
		truncated: allTurns.length > selected.length || selected.some((turn) => turn.truncated),
	};
}

type MutableTurn = AgentSessionTurnSummary & { hasUser: boolean };

function createMutableTurn(): MutableTurn {
	return {
		user: "",
		assistant: "",
		toolCalls: 0,
		messageCount: 0,
		truncated: false,
		hasUser: false,
	};
}

function appendText(current: string, next: string, maxChars: number): { text: string; truncated: boolean } {
	if (!next) return { text: current, truncated: false };
	const combined = current ? `${current}\n${next}` : next;
	return combined.length <= maxChars
		? { text: combined, truncated: false }
		: { text: combined.slice(0, maxChars), truncated: true };
}

function requireString(payload: Record<string, unknown>, key: string, label: string): string {
	const value = payload[key];
	if (typeof value !== "string" || !value.trim()) throw new Error(`${label} must be a non-empty string`);
	return value.trim();
}

function normalizeOptionalInteger(value: unknown, label: string): number | undefined {
	if (value === undefined) return undefined;
	if (typeof value !== "number" || !Number.isInteger(value))
		throw new Error(`${label} must be an integer when provided`);
	return value;
}

function normalizeDelivery(value: unknown): AgentSessionDelivery {
	if (value === "steer" || value === "follow_up") return value;
	throw new Error("agent.sessions.send delivery must be 'steer' or 'follow_up'");
}

function clampInteger(value: number, min: number, max: number, label: string): number {
	if (!Number.isInteger(value)) throw new Error(`${label} must be an integer`);
	if (value < min || value > max) throw new Error(`${label} must be between ${min} and ${max}`);
	return value;
}
