1import type { EnvironmentId, ProjectId } from "@t3tools/contracts";
2
3import type { OpenPreviewMutation } from "./browser/openFileInPreview";
4import type { ClosedView, ClosedViewEntry } from "./closedViewStore";
5import type { PullRequestListPreferences } from "./components/pullRequest/pullRequestListPreferences";
6import { openPreviewSession } from "./components/preview/openPreviewSession";
7import {
8 type RightPanelSurface,
9 type ThreadRightPanelState,
10 useRightPanelStore,
11} from "./rightPanelStore";
12
13export interface ReopenOwnerState {
14 /** False when the entry's environment is not in the catalog. */
15 environmentKnown: boolean;
16 catalogReady: boolean;
17 /** Whether the owning thread or draft still exists locally. */
18 ownerExists: boolean;
19 /** A live shell that lacks the thread means it was deleted; a cached one may just be stale. */
20 shellLive: boolean;
21 panel: ThreadRightPanelState;
22}
23
24/**
25 * Picks the history entry the next reopen press restores, newest first. Entries whose
26 * environment is gone or whose tab is already open are dropped. A thread missing from a
27 * live shell is skipped but kept; anything not yet knowable stops the scan with no restore.
28 */
29export function planNextReopen(
30 entries: readonly ClosedViewEntry[],
31 ownerState: (entry: ClosedViewEntry) => ReopenOwnerState,
32): { drop: ClosedViewEntry[]; restore: ClosedViewEntry | null } {
33 const drop: ClosedViewEntry[] = [];
34 for (const entry of entries) {
35 const owner = ownerState(entry);
36 if (!owner.environmentKnown) {
37 if (!owner.catalogReady) break;
38 drop.push(entry);
39 continue;
40 }
41 if (!owner.ownerExists) {
42 if (owner.shellLive) continue;
43 break;
44 }
45 const alreadyOpen =
46 entry.kind === "panel-tab"
47 ? owner.panel.isOpen && owner.panel.surfaces.some((s) => s.id === entry.surface.id)
48 : owner.panel.surfaces.some(
49 (s) => s.kind === "preview" && s.resourceId === entry.snapshot.tabId,
50 );
51 if (alreadyOpen) {
52 drop.push(entry);
53 continue;
54 }
55 return { drop, restore: entry };
56 }
57 return { drop, restore: null };
58}
59
60type PullRequestsSearchLike = Partial<PullRequestListPreferences> & {
61 repository?: string;
62 number?: number;
63 selectedProjectId?: ProjectId;
64 selectedHost?: string;
65 selectedEnvironmentId?: EnvironmentId;
66};
67
68/** Selects the restored pull request on the Pull Requests page, keeping the list filters. */
69export function pullRequestsSearchForRestore<S extends PullRequestsSearchLike>(
70 previous: S,
71 selected: RightPanelSurface | null,
72): S & PullRequestListPreferences {
73 const {
74 repository: _repository,
75 number: _number,
76 selectedProjectId: _projectId,
77 selectedHost: _host,
78 selectedEnvironmentId: _environmentId,
79 ...filters
80 } = previous;
81 return {
82 ...filters,
83 involvement: previous.involvement ?? "all",
84 state: previous.state ?? "open",
85 ...(selected?.kind === "pull-request"
86 ? {
87 repository: selected.repository,
88 number: selected.number,
89 selectedProjectId: selected.projectId as ProjectId,
90 ...(selected.host === undefined ? {} : { selectedHost: selected.host }),
91 ...(selected.environmentId === undefined
92 ? {}
93 : { selectedEnvironmentId: selected.environmentId as EnvironmentId }),
94 }
95 : {}),
96 } as S & PullRequestListPreferences;
97}
98
99export async function reopenClosedView(
100 view: ClosedView,
101 options: {
102 openPreview: OpenPreviewMutation;
103 workspaceAvailable: boolean;
104 },
105): Promise<boolean> {
106 const panels = useRightPanelStore.getState();
107 const ref = view.threadRef;
108
109 if (view.kind === "browser") {
110 const url = view.snapshot.navStatus._tag === "Idle" ? undefined : view.snapshot.navStatus.url;
111 const result = await openPreviewSession({
112 openPreview: options.openPreview,
113 threadRef: ref,
114 ...(url === undefined ? {} : { url }),
115 ...(view.snapshot.viewport === undefined ? {} : { viewport: view.snapshot.viewport }),
116 ...(view.snapshot.profileId === undefined ? {} : { profileId: view.snapshot.profileId }),
117 });
118 if (result._tag === "Failure") return false;
119 panels.openBrowser(ref, result.value.tabId);
120 return true;
121 }
122 const surface = view.surface;
123 if (
124 !options.workspaceAvailable &&
125 (surface.kind === "files" || (surface.kind === "file" && !surface.attachment))
126 )
127 return false;
128 switch (surface.kind) {
129 case "preview":
130 if (surface.resourceId !== null) return false;
131 panels.openBrowser(ref, null);
132 break;
133 case "file":
134 if (surface.attachment) panels.openAttachment(ref, surface.attachment);
135 else panels.openFile(ref, surface.relativePath, surface.revealLine ?? undefined);
136 break;
137 case "device":
138 if (surface.target) {
139 panels.openDevice(ref, surface.target);
140 if (surface.title) panels.renameDevice(ref, surface.id, surface.title);
141 } else panels.open(ref, "device");
142 break;
143 case "pull-request":
144 panels.openPullRequest(ref, surface);
145 break;
146 default:
147 panels.open(ref, surface.kind);
148 }
149 return true;
150}