import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell"; import { repositoryGroupingKeyOf, type EnvironmentId } from "@t3tools/contracts"; import { scopedProjectKey } from "../../lib/scopedEntities"; import type { HomeProjectScope } from "../home/homeThreadList"; type DraftProjectSelectionResolution = | { readonly kind: "preserve" } | { readonly kind: "select"; readonly project: EnvironmentProject } | { readonly kind: "pick" } | { readonly kind: "dismiss" }; /** * The copy of a project a new thread starts in: the preferred environment's, then the * representative. A connected copy wins over a disconnected one. */ export function getProjectScopeSelectionTarget( scope: HomeProjectScope, preferredEnvironmentId: EnvironmentId | null, connectedEnvironmentIds: ReadonlySet, ): EnvironmentProject { const rank = (project: EnvironmentProject) => project.environmentId === preferredEnvironmentId ? 0 : project === scope.representative ? 1 : 2; const ordered = [...scope.projects].sort((left, right) => rank(left) - rank(right)); return ( ordered.find((project) => connectedEnvironmentIds.has(project.environmentId)) ?? ordered[0] ?? scope.representative ); } export function filterProjectScopes( scopes: ReadonlyArray, searchText: string, ): ReadonlyArray { const query = searchText.trim().toLowerCase(); if (!query) return scopes; return scopes.filter( (scope) => scope.title.toLowerCase().includes(query) || scope.projects.some( (project) => project.title.toLowerCase().includes(query) || project.workspaceRoot.toLowerCase().includes(query), ), ); } export function resolveEnvironmentProjectMatch( projectsOnTarget: ReadonlyArray, selectedProject: EnvironmentProject | null, ): EnvironmentProject | null { const repositoryKey = selectedProject?.repositoryIdentity ? repositoryGroupingKeyOf(selectedProject.repositoryIdentity) : null; // Use || null: placeholder workspaceRoot can be empty, which supplies no useful basename. const workspaceBasename = selectedProject?.workspaceRoot.split("/").at(-1) || null; // Use weaker signals only when identity is unknown; different known repositories never match by basename or title. const isKnownMismatch = (project: EnvironmentProject) => { const projectKey = project.repositoryIdentity ? repositoryGroupingKeyOf(project.repositoryIdentity) : null; return repositoryKey !== null && projectKey !== null && projectKey !== repositoryKey; }; return ( (repositoryKey !== null ? projectsOnTarget.find( (project) => (project.repositoryIdentity ? repositoryGroupingKeyOf(project.repositoryIdentity) : null) === repositoryKey, ) : undefined) ?? (workspaceBasename !== null ? projectsOnTarget.find( (project) => !isKnownMismatch(project) && project.workspaceRoot.split("/").at(-1) === workspaceBasename, ) : undefined) ?? (selectedProject !== null ? projectsOnTarget.find( (project) => !isKnownMismatch(project) && project.title === selectedProject.title, ) : undefined) ?? projectsOnTarget[0] ?? null ); } export function resolveDraftProjectSelection( selectedProjectKey: string | null, projects: ReadonlyArray, projectScopes: ReadonlyArray, draft: { readonly incomingShareId: string | undefined; /** The draft's first project picker closed and the draft is showing again. */ readonly pickerClosed: boolean; }, ): DraftProjectSelectionResolution { const hasExplicitProjectSelection = selectedProjectKey !== null && projects.some( (project) => scopedProjectKey(project.environmentId, project.id) === selectedProjectKey, ); if (hasExplicitProjectSelection) { return { kind: "preserve" }; } // A share draft holds content the user sent here, so cancelling the picker must not drop it. if (draft.pickerClosed && selectedProjectKey === null && draft.incomingShareId === undefined) { return { kind: "dismiss" }; } const onlyScope = draft.incomingShareId !== undefined && projectScopes.length === 1 ? projectScopes[0] : null; return onlyScope ? { kind: "select", project: onlyScope.representative } : { kind: "pick" }; } type ProjectRef = { readonly environmentId: string; readonly projectId: string }; export function newTaskDestination(options: { readonly projects: ReadonlyArray; readonly requestId: string; readonly useLastProject: boolean | undefined; readonly currentProject: ProjectRef | undefined; readonly lastProject: ProjectRef | undefined; }) { const find = (ref: ProjectRef | undefined) => ref && options.projects.find( (project) => project.environmentId === ref.environmentId && project.id === ref.projectId, ); const project = options.useLastProject === false ? undefined : (find(options.currentProject) ?? find(options.lastProject)); return project ? { draft: { requestId: options.requestId, environmentId: String(project.environmentId), projectId: String(project.id), }, } : { draft: { requestId: options.requestId } }; }