import { findPaneById, type SplitPane, type WorkspaceLayout, } from "@/stores/workspace-layout-store"; import type { WorkspaceTab, WorkspaceTabTarget } from "@/stores/workspace-tabs-store"; import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types"; import { buildDeterministicWorkspaceTabId, normalizeWorkspaceTabTarget, workspaceTabTargetsEqual, } from "@/utils/workspace-tab-identity"; export interface WorkspaceDerivedTab { descriptor: WorkspaceTabDescriptor; } export interface WorkspacePaneState { pane: SplitPane | null; tabs: WorkspaceDerivedTab[]; focusedTabId: string | null; activeTabId: string | null; activeTab: WorkspaceDerivedTab | null; } interface NormalizeWorkspacePaneTabsResult { tabs: WorkspaceDerivedTab[]; openTabIds: Set; } function trimNonEmpty(value: string | null | undefined): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function normalizeWorkspaceTab(tab: WorkspaceTab): WorkspaceTab | null { if (!tab || typeof tab !== "object") { return null; } const tabId = trimNonEmpty(tab.tabId); if (!tabId) { return null; } const target = normalizeWorkspaceTabTarget(tab.target); if (!target) { return null; } return { tabId, target, createdAt: tab.createdAt, }; } function orderPaneTabs(input: { pane: SplitPane | null; tabs: WorkspaceTab[] }): WorkspaceTab[] { if (!input.pane) { return input.tabs; } const tabsById = new Map(); for (const tab of input.tabs) { tabsById.set(tab.tabId, tab); } const orderedTabs: WorkspaceTab[] = []; for (const tabId of input.pane.tabIds) { const tab = tabsById.get(tabId); if (tab) { orderedTabs.push(tab); } } return orderedTabs; } function normalizeWorkspacePaneTabs(tabs: WorkspaceTab[]): NormalizeWorkspacePaneTabsResult { const nextTabs: WorkspaceDerivedTab[] = []; const openTabIds = new Set(); for (const tab of tabs) { const normalizedTab = normalizeWorkspaceTab(tab); if (!normalizedTab || openTabIds.has(normalizedTab.tabId)) { continue; } openTabIds.add(normalizedTab.tabId); nextTabs.push({ descriptor: { key: normalizedTab.tabId, tabId: normalizedTab.tabId, kind: normalizedTab.target.kind, target: normalizedTab.target, }, }); } return { tabs: nextTabs, openTabIds, }; } function getActiveTabId(input: { tabs: WorkspaceDerivedTab[]; openTabIds: Set; focusedTabId?: string | null; preferredTarget?: WorkspaceTabTarget | null; }): string | null { const focusedTabId = trimNonEmpty(input.focusedTabId); const preferredTarget = normalizeWorkspaceTabTarget(input.preferredTarget ?? null); const preferredTabId = (() => { if (!preferredTarget) { return null; } const matchingTab = input.tabs.find((tab) => workspaceTabTargetsEqual(tab.descriptor.target, preferredTarget)) ?? null; return matchingTab?.descriptor.tabId ?? buildDeterministicWorkspaceTabId(preferredTarget); })(); if (preferredTabId && input.openTabIds.has(preferredTabId)) { return preferredTabId; } if (focusedTabId && input.openTabIds.has(focusedTabId)) { return focusedTabId; } return input.tabs[0]?.descriptor.tabId ?? null; } function getPane(input: { layout: WorkspaceLayout | null; pane?: SplitPane | null; paneId?: string | null; }): SplitPane | null { if (input.pane) { return input.pane; } const layout = input.layout; if (!layout) { return null; } const resolvedPaneId = trimNonEmpty(input.paneId) ?? layout.focusedPaneId; if (!resolvedPaneId) { return null; } return findPaneById(layout.root, resolvedPaneId); } export function deriveWorkspacePaneState(input: { layout?: WorkspaceLayout | null; pane?: SplitPane | null; paneId?: string | null; tabs: WorkspaceTab[]; focusedTabId?: string | null; preferredTarget?: WorkspaceTabTarget | null; }): WorkspacePaneState { const pane = getPane({ layout: input.layout ?? null, pane: input.pane ?? null, paneId: input.paneId, }); const orderedTabs = orderPaneTabs({ pane, tabs: input.tabs, }); const normalizedTabs = normalizeWorkspacePaneTabs(orderedTabs); const focusedTabId = pane?.focusedTabId ?? trimNonEmpty(input.focusedTabId) ?? null; const activeTabId = getActiveTabId({ tabs: normalizedTabs.tabs, openTabIds: normalizedTabs.openTabIds, focusedTabId, preferredTarget: input.preferredTarget, }); return { pane, tabs: normalizedTabs.tabs, focusedTabId, activeTabId, activeTab: activeTabId ? (normalizedTabs.tabs.find((tab) => tab.descriptor.tabId === activeTabId) ?? null) : null, }; } export function getWorkspacePaneDescriptors(input: { layout?: WorkspaceLayout | null; pane?: SplitPane | null; paneId?: string | null; tabs: WorkspaceTab[]; }): WorkspaceTabDescriptor[] { return deriveWorkspacePaneState(input).tabs.map((tab) => tab.descriptor); }