import { mkdtemp, rm, mkdir } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { openDatabase, closeDatabase } from "../../db/database.js";
import { createProject } from "../../db/repositories/projects.js";
// Suppress logger output in tests
vi.mock("../../utils/logger.js", () => ({
    logger: {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
    },
}));
// Mock node-pty with captured callbacks
let _onDataCallback = null;
let onExitCallback = null;
const mockWrite = vi.fn();
const mockResize = vi.fn();
const mockKill = vi.fn();
const mockSpawn = vi.fn().mockImplementation(() => ({
    pid: 12345,
    onData: (cb) => {
        _onDataCallback = cb;
    },
    onExit: (cb) => {
        onExitCallback = cb;
    },
    write: mockWrite,
    resize: mockResize,
    kill: mockKill,
}));
vi.mock("node-pty", () => ({
    spawn: (...args) => mockSpawn(...args),
}));
// Mock terminal-sessions DB
const mockCloseTerminalSession = vi.fn();
vi.mock("../../db/repositories/terminal-sessions.js", () => ({
    createTerminalSession: vi.fn().mockReturnValue({
        terminal_id: "mock-terminal-id",
        project_id: "mock-project-id",
        pid: 12345,
        created_at: "2026-02-20T00:00:00.000Z",
        closed_at: null,
    }),
    closeTerminalSession: (...args) => mockCloseTerminalSession(...args),
}));
import { createTerminal, setTerminalSendFn, killAllTerminals, getTerminal, } from "../terminal-manager.js";
describe("terminal_exit emission", () => {
    let tempDir;
    let projectDir;
    let dbPath;
    let projectId;
    let sentMessages;
    beforeEach(async () => {
        vi.clearAllMocks();
        _onDataCallback = null;
        onExitCallback = null;
        tempDir = await mkdtemp(join(tmpdir(), "pocketcoder-terminal-exit-test-"));
        projectDir = join(tempDir, "project");
        dbPath = join(tempDir, "daemon.db");
        await mkdir(projectDir);
        closeDatabase();
        openDatabase(dbPath);
        const project = createProject("test-project", projectDir);
        projectId = project.project_id;
        sentMessages = [];
        setTerminalSendFn((msg) => {
            sentMessages.push(msg);
        });
    });
    afterEach(async () => {
        killAllTerminals();
        closeDatabase();
        await rm(tempDir, { recursive: true, force: true });
    });
    it("should emit exit event on natural exit", () => {
        const terminalId = createTerminal(projectId, projectDir);
        expect(onExitCallback).toBeDefined();
        // Simulate natural PTY exit (exit code 0, no signal)
        onExitCallback({ exitCode: 0 });
        const exitMessages = sentMessages.filter((m) => m.type === "terminal_exit");
        expect(exitMessages).toHaveLength(1);
        const event = exitMessages[0];
        expect(event.type).toBe("terminal_exit");
        expect(event.payload.terminalId).toBe(terminalId);
        expect(event.payload.exitCode).toBe(0);
        expect(event.payload.signal).toBeUndefined();
    });
    it("should emit exit event on kill", () => {
        const terminalId = createTerminal(projectId, projectDir);
        // Simulate PTY exit due to kill (non-zero exit code)
        onExitCallback({ exitCode: 1 });
        const exitMessages = sentMessages.filter((m) => m.type === "terminal_exit");
        expect(exitMessages).toHaveLength(1);
        const event = exitMessages[0];
        expect(event.type).toBe("terminal_exit");
        expect(event.payload.terminalId).toBe(terminalId);
        expect(event.payload.exitCode).toBe(1);
    });
    it("should include signal when killed", () => {
        const terminalId = createTerminal(projectId, projectDir);
        // Simulate PTY exit with signal (e.g., SIGTERM = 15)
        onExitCallback({ exitCode: 143, signal: 15 });
        const exitMessages = sentMessages.filter((m) => m.type === "terminal_exit");
        expect(exitMessages).toHaveLength(1);
        const event = exitMessages[0];
        expect(event.payload.terminalId).toBe(terminalId);
        expect(event.payload.exitCode).toBe(143);
        expect(event.payload.signal).toBe(15);
    });
    it("should cleanup terminal resources on exit", () => {
        const terminalId = createTerminal(projectId, projectDir);
        // Terminal should be in the active map
        expect(getTerminal(terminalId)).toBeDefined();
        // Simulate exit
        onExitCallback({ exitCode: 0 });
        // Terminal should be removed from the active map
        expect(getTerminal(terminalId)).toBeUndefined();
        // Should close the terminal session in DB
        expect(mockCloseTerminalSession).toHaveBeenCalledWith(terminalId);
    });
    it("should not emit exit event when sendFn is null", () => {
        createTerminal(projectId, projectDir);
        // Nullify sendFn
        sentMessages = [];
        setTerminalSendFn(null);
        // Simulate exit
        onExitCallback({ exitCode: 0 });
        // No messages should be sent
        expect(sentMessages).toHaveLength(0);
        // But cleanup should still happen (closeTerminalSession called)
        expect(mockCloseTerminalSession).toHaveBeenCalled();
    });
    it("should handle exit with signal 0", () => {
        const terminalId = createTerminal(projectId, projectDir);
        // Signal 0 is a valid signal value — should be included
        onExitCallback({ exitCode: 0, signal: 0 });
        const exitMessages = sentMessages.filter((m) => m.type === "terminal_exit");
        expect(exitMessages).toHaveLength(1);
        const event = exitMessages[0];
        expect(event.payload.terminalId).toBe(terminalId);
        expect(event.payload.exitCode).toBe(0);
        // signal: 0 is falsy but !== undefined, so it should be included
        // The implementation uses `signal !== undefined` check
        expect(event.payload.signal).toBe(0);
    });
});
//# sourceMappingURL=terminal-exit.test.js.map