1import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime";
2import type { ScopedThreadRef } from "@t3tools/contracts";
3import { resolveTerminalSessionLabel } from "@t3tools/shared/terminalLabels";
4
5import { readLocalApi } from "~/localApi";
6import { appAtomRegistry } from "~/rpc/atomRegistry";
7import { terminalEnvironment } from "~/state/terminal";
8
9let pendingConfirmations = 0;
10
11export function isTerminalCloseConfirmPending(): boolean {
12 return pendingConfirmations > 0;
13}
14
15export async function confirmTerminalClose(
16 threadRef: ScopedThreadRef,
17 terminalIds: readonly [string, ...string[]],
18): Promise<boolean> {
19 const localApi = readLocalApi();
20 if (!localApi) return true;
21 pendingConfirmations += 1;
22 try {
23 const metadata = await executeAtomQuery(
24 appAtomRegistry,
25 terminalEnvironment.metadataSnapshot({ environmentId: threadRef.environmentId, input: null }),
26 { refresh: true, reportFailure: false, signal: AbortSignal.timeout(3_000) },
27 );
28 const terminals = terminalIds.map((terminalId) =>
29 metadata._tag === "Success"
30 ? metadata.value.find(
31 (terminal) =>
32 terminal.threadId === threadRef.threadId && terminal.terminalId === terminalId,
33 )
34 : undefined,
35 );
36 if (terminals.every((terminal) => terminal !== undefined && !terminal.hasRunningSubprocess)) {
37 return true;
38 }
39
40 const labels = terminalIds.map((terminalId, index) =>
41 resolveTerminalSessionLabel(terminalId, terminals[index]),
42 );
43 return await localApi.dialogs.confirm(
44 labels.length === 1
45 ? [
46 `Close terminal "${labels[0]}"?`,
47 "This stops the running process and clears its history.",
48 ].join("\n")
49 : [
50 `Close ${labels.length} terminals?`,
51 `This stops their running processes and clears their histories: ${labels
52 .map((label) => `"${label}"`)
53 .join(", ")}.`,
54 ].join("\n"),
55 { variant: "destructive" },
56 );
57 } catch {
58 return false;
59 } finally {
60 pendingConfirmations -= 1;
61 }
62}