import { randomUUID } from "node:crypto";
import { mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { DEFAULT_BUILTIN_TOOL_NAMES } from "../../core/profiles.js";
import { findPiPackage } from "../../pi-packages/store.js";
const CUSTOM_AGENT_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
export class CustomAgentStore {
    db;
    constructor(path) {
        const resolvedPath = path === ":memory:" ? path : resolve(path);
        if (resolvedPath !== ":memory:")
            mkdirSync(dirname(resolvedPath), { recursive: true });
        this.db = new DatabaseSync(resolvedPath);
        this.db.exec("PRAGMA busy_timeout = 5000");
        if (resolvedPath !== ":memory:")
            this.db.exec("PRAGMA journal_mode = WAL");
        this.db.exec(`
			CREATE TABLE IF NOT EXISTS chat_agents (
				id TEXT PRIMARY KEY,
				profile_name TEXT NOT NULL UNIQUE,
				owner_scope TEXT NOT NULL,
				display_name TEXT NOT NULL,
				description TEXT,
				native_tools_json TEXT NOT NULL,
				skills_json TEXT NOT NULL,
				context_files_json TEXT NOT NULL,
				subagents_json TEXT NOT NULL,
				mcp_servers_json TEXT NOT NULL DEFAULT '[]',
				pi_packages_json TEXT NOT NULL DEFAULT '[]',
				builtin_tools TEXT NOT NULL,
				builtin_tool_names_json TEXT NOT NULL DEFAULT '["read","bash","edit","write"]',
				auto_context_files INTEGER NOT NULL DEFAULT 1,
				run_control INTEGER NOT NULL,
				created_at TEXT NOT NULL,
				updated_at TEXT NOT NULL,
				archived_at TEXT
			);

			CREATE INDEX IF NOT EXISTS idx_chat_agents_owner
				ON chat_agents(owner_scope, updated_at);
		`);
        this.migrateArchivedAtColumn();
        this.migrateAutoContextFilesColumn();
        this.migrateMcpServersColumn();
        this.migratePiPackagesColumn();
        this.migrateBuiltinToolNamesColumn();
        this.migrateLegacyProfileNames();
    }
    list(ownerScope, options = {}) {
        this.migrateLegacyProfileNames();
        const archivedClause = options.includeArchived ? "" : " AND archived_at IS NULL";
        const rows = ownerScope
            ? this.db.prepare(`SELECT * FROM chat_agents WHERE owner_scope = ?${archivedClause} ORDER BY updated_at DESC`).all(ownerScope)
            : this.db.prepare(`SELECT * FROM chat_agents WHERE 1 = 1${archivedClause} ORDER BY updated_at DESC`).all();
        return rows.map(agentFromRow);
    }
    get(id) {
        this.migrateLegacyProfileNames();
        const row = this.db.prepare("SELECT * FROM chat_agents WHERE id = ?").get(id);
        return row ? agentFromRow(row) : undefined;
    }
    create(input) {
        this.migrateLegacyProfileNames();
        const now = new Date().toISOString();
        const id = `agent_${randomUUID()}`;
        const profileName = input.displayName;
        this.requireProfileNameAvailable(profileName);
        const agent = {
            id,
            profileName,
            ownerScope: input.ownerScope,
            displayName: input.displayName,
            description: input.description,
            nativeTools: [...(input.nativeTools ?? [])],
            skills: [...(input.skills ?? [])],
            contextFiles: [...(input.contextFiles ?? [])],
            subagents: sanitizeSubagents(input.subagents ?? []),
            mcpServers: uniqueStrings(input.mcpServers ?? []),
            piPackages: sanitizePiPackages(input.piPackages ?? []),
            builtinTools: input.builtinTools ?? "default",
            builtinToolNames: sanitizeBuiltinToolNames(input.builtinToolNames),
            autoContextFiles: input.autoContextFiles ?? true,
            runControl: input.runControl ?? false,
            createdAt: now,
            updatedAt: now,
        };
        this.insert(agent);
        const created = this.get(id);
        if (!created)
            throw new Error(`Failed to create custom agent "${id}"`);
        return created;
    }
    update(id, input) {
        this.migrateLegacyProfileNames();
        const existing = this.get(id);
        if (!existing)
            return undefined;
        const profileName = input.displayName ?? existing.displayName;
        this.requireProfileNameAvailable(profileName, id);
        const updated = {
            ...existing,
            profileName,
            displayName: input.displayName ?? existing.displayName,
            description: input.description === undefined ? existing.description : input.description,
            nativeTools: input.nativeTools ? [...input.nativeTools] : existing.nativeTools,
            skills: input.skills ? [...input.skills] : existing.skills,
            contextFiles: input.contextFiles ? [...input.contextFiles] : existing.contextFiles,
            subagents: input.subagents ? sanitizeSubagents(input.subagents) : existing.subagents,
            mcpServers: input.mcpServers ? uniqueStrings(input.mcpServers) : existing.mcpServers,
            piPackages: input.piPackages ? sanitizePiPackages(input.piPackages) : existing.piPackages,
            builtinTools: input.builtinTools ?? existing.builtinTools,
            builtinToolNames: input.builtinToolNames ? sanitizeBuiltinToolNames(input.builtinToolNames) : existing.builtinToolNames,
            autoContextFiles: input.autoContextFiles ?? existing.autoContextFiles,
            runControl: input.runControl ?? existing.runControl,
            updatedAt: new Date().toISOString(),
        };
        this.db
            .prepare(`
				UPDATE chat_agents SET
					profile_name = ?,
					display_name = ?,
					description = ?,
					native_tools_json = ?,
					skills_json = ?,
					context_files_json = ?,
					subagents_json = ?,
					mcp_servers_json = ?,
					pi_packages_json = ?,
					builtin_tools = ?,
					builtin_tool_names_json = ?,
					auto_context_files = ?,
					run_control = ?,
					updated_at = ?
				WHERE id = ?
			`)
            .run(updated.profileName, updated.displayName, updated.description ?? null, JSON.stringify(updated.nativeTools), JSON.stringify(updated.skills), JSON.stringify(updated.contextFiles), JSON.stringify(sanitizeSubagents(updated.subagents)), JSON.stringify(updated.mcpServers), JSON.stringify(updated.piPackages), updated.builtinTools, JSON.stringify(updated.builtinToolNames), updated.autoContextFiles ? 1 : 0, updated.runControl ? 1 : 0, updated.updatedAt, id);
        return this.get(id);
    }
    setArchived(id, archived) {
        this.migrateLegacyProfileNames();
        const existing = this.get(id);
        if (!existing)
            return undefined;
        const archivedAt = archived ? existing.archivedAt ?? new Date().toISOString() : null;
        this.db
            .prepare("UPDATE chat_agents SET archived_at = ?, updated_at = ? WHERE id = ?")
            .run(archivedAt, new Date().toISOString(), id);
        return this.get(id);
    }
    delete(id) {
        const result = this.db.prepare("DELETE FROM chat_agents WHERE id = ?").run(id);
        return Number(result.changes ?? 0) > 0;
    }
    close() {
        this.db.close();
    }
    insert(agent) {
        this.db
            .prepare(`
				INSERT INTO chat_agents (
					id,
					profile_name,
					owner_scope,
					display_name,
					description,
					native_tools_json,
					skills_json,
					context_files_json,
					subagents_json,
					mcp_servers_json,
					pi_packages_json,
					builtin_tools,
					builtin_tool_names_json,
					auto_context_files,
					run_control,
					created_at,
					updated_at,
					archived_at
				) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
			`)
            .run(agent.id, agent.profileName, agent.ownerScope, agent.displayName, agent.description ?? null, JSON.stringify(agent.nativeTools), JSON.stringify(agent.skills), JSON.stringify(agent.contextFiles), JSON.stringify(sanitizeSubagents(agent.subagents)), JSON.stringify(agent.mcpServers), JSON.stringify(agent.piPackages), agent.builtinTools, JSON.stringify(agent.builtinToolNames), agent.autoContextFiles ? 1 : 0, agent.runControl ? 1 : 0, agent.createdAt, agent.updatedAt, agent.archivedAt ?? null);
    }
    requireProfileNameAvailable(profileName, currentId) {
        const row = this.db.prepare("SELECT id FROM chat_agents WHERE profile_name = ?").get(profileName);
        if (row && row.id !== currentId)
            throw new Error(`Agent name "${profileName}" already exists`);
    }
    migrateLegacyProfileNames() {
        const rows = this.db.prepare("SELECT id, profile_name, display_name FROM chat_agents ORDER BY created_at ASC").all();
        const used = new Set(rows.map((row) => row.profile_name));
        for (const row of rows) {
            if (!row.profile_name.startsWith("custom-agent:agent_"))
                continue;
            used.delete(row.profile_name);
            const nextName = uniqueAgentName(agentNameCandidate(row.display_name, row.id), used);
            used.add(nextName);
            this.db
                .prepare("UPDATE chat_agents SET profile_name = ?, display_name = ? WHERE id = ?")
                .run(nextName, nextName, row.id);
        }
    }
    migrateArchivedAtColumn() {
        const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
        if (!columns.has("archived_at")) {
            this.db.prepare("ALTER TABLE chat_agents ADD COLUMN archived_at TEXT").run();
        }
    }
    migrateAutoContextFilesColumn() {
        const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
        if (!columns.has("auto_context_files")) {
            this.db.prepare("ALTER TABLE chat_agents ADD COLUMN auto_context_files INTEGER NOT NULL DEFAULT 1").run();
        }
    }
    migrateMcpServersColumn() {
        const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
        if (!columns.has("mcp_servers_json")) {
            this.db.prepare("ALTER TABLE chat_agents ADD COLUMN mcp_servers_json TEXT NOT NULL DEFAULT '[]'").run();
        }
    }
    migratePiPackagesColumn() {
        const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
        if (!columns.has("pi_packages_json")) {
            this.db.prepare("ALTER TABLE chat_agents ADD COLUMN pi_packages_json TEXT NOT NULL DEFAULT '[]'").run();
        }
    }
    migrateBuiltinToolNamesColumn() {
        const columns = new Set(this.db.prepare("PRAGMA table_info(chat_agents)").all().map((column) => column.name));
        if (!columns.has("builtin_tool_names_json")) {
            this.db.prepare("ALTER TABLE chat_agents ADD COLUMN builtin_tool_names_json TEXT NOT NULL DEFAULT '[\"read\",\"bash\",\"edit\",\"write\"]'").run();
        }
    }
}
export function createDefaultCustomAgentStore(cwd = process.cwd()) {
    return new CustomAgentStore(resolve(cwd, ".pibo/chat-agents.sqlite"));
}
function agentFromRow(row) {
    return {
        id: row.id,
        profileName: row.profile_name,
        ownerScope: row.owner_scope,
        displayName: row.display_name,
        description: row.description ?? undefined,
        nativeTools: parseStringArray(row.native_tools_json),
        skills: parseStringArray(row.skills_json),
        contextFiles: parseStringArray(row.context_files_json),
        subagents: parseSubagents(row.subagents_json),
        mcpServers: parseStringArray(row.mcp_servers_json),
        piPackages: parseStringArray(row.pi_packages_json),
        builtinTools: row.builtin_tools,
        builtinToolNames: sanitizeBuiltinToolNames(parseStringArray(row.builtin_tool_names_json)),
        autoContextFiles: row.auto_context_files !== 0,
        runControl: row.run_control === 1,
        createdAt: row.created_at,
        updatedAt: row.updated_at,
        archivedAt: row.archived_at ?? undefined,
    };
}
function parseStringArray(value) {
    try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
    }
    catch {
        return [];
    }
}
function uniqueStrings(value) {
    return [...new Set(value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()))];
}
function sanitizePiPackages(value) {
    const packages = uniqueStrings(value);
    for (const pkg of packages) {
        if (!findPiPackage(pkg))
            throw new Error(`Unknown Pi package "${pkg}"`);
    }
    return packages;
}
function sanitizeBuiltinToolNames(value) {
    const selected = new Set(uniqueStrings(value ?? DEFAULT_BUILTIN_TOOL_NAMES));
    return DEFAULT_BUILTIN_TOOL_NAMES.filter((name) => selected.has(name));
}
function parseSubagents(value) {
    try {
        const parsed = JSON.parse(value);
        return Array.isArray(parsed)
            ? sanitizeSubagents(parsed)
            : [];
    }
    catch {
        return [];
    }
}
function sanitizeSubagents(value) {
    return value.flatMap((item) => {
        if (!item || typeof item !== "object" || Array.isArray(item))
            return [];
        const candidate = item;
        if (typeof candidate.name !== "string" || typeof candidate.targetProfile !== "string")
            return [];
        const subagent = {
            name: candidate.name,
            targetProfile: candidate.targetProfile,
        };
        if (typeof candidate.description === "string")
            subagent.description = candidate.description;
        if (typeof candidate.timeoutMs === "number")
            subagent.timeoutMs = candidate.timeoutMs;
        if (typeof candidate.maxDepth === "number")
            subagent.maxDepth = candidate.maxDepth;
        return [subagent];
    });
}
export function isValidCustomAgentName(name) {
    return CUSTOM_AGENT_NAME_PATTERN.test(name);
}
function agentNameCandidate(displayName, id) {
    const candidate = displayName
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-+|-+$/g, "")
        .replace(/-+/g, "-");
    if (isValidCustomAgentName(candidate))
        return candidate;
    return `agent-${id.replace(/^agent_/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")}`;
}
function uniqueAgentName(baseName, used) {
    let name = baseName;
    let suffix = 2;
    while (used.has(name)) {
        name = `${baseName}-${suffix}`;
        suffix += 1;
    }
    return name;
}
