import { scopedProjectKey, scopeProjectRef } from "../environment/scoped.ts"; import type { ScopedProjectRef, SidebarProjectGroupingMode } from "@t3tools/contracts"; import type { ClientSettings } from "@t3tools/contracts/settings"; import type { EnvironmentProject } from "./models.ts"; import { normalizeProjectPathForComparison } from "./projects.ts"; export interface ProjectGroupingSettings { readonly sidebarProjectGroupingMode: SidebarProjectGroupingMode; readonly sidebarProjectGroupingOverrides: Record; } export type ProjectGroupingMode = SidebarProjectGroupingMode; export function selectProjectGroupingSettings(settings: ClientSettings): ProjectGroupingSettings { return { sidebarProjectGroupingMode: settings.sidebarProjectGroupingMode, sidebarProjectGroupingOverrides: settings.sidebarProjectGroupingOverrides, }; } function uniqueNonEmptyValues(values: ReadonlyArray): string[] { const seen = new Set(); const unique: string[] = []; for (const value of values) { const trimmed = value?.trim(); if (!trimmed || seen.has(trimmed)) { continue; } seen.add(trimmed); unique.push(trimmed); } return unique; } function deriveRepositoryRelativeProjectPath( project: Pick, ): string | null { const rootPath = project.repositoryIdentity?.rootPath?.trim(); if (!rootPath) { return null; } const normalizedProjectPath = normalizeProjectPathForComparison(project.workspaceRoot); const normalizedRootPath = normalizeProjectPathForComparison(rootPath); if (normalizedProjectPath.length === 0 || normalizedRootPath.length === 0) { return null; } if (normalizedProjectPath === normalizedRootPath) { return ""; } const separator = normalizedRootPath.includes("\\") ? "\\" : "/"; const rootPrefix = `${normalizedRootPath}${separator}`; if (!normalizedProjectPath.startsWith(rootPrefix)) { return null; } return normalizedProjectPath.slice(rootPrefix.length).replaceAll("\\", "/"); } export function derivePhysicalProjectKeyFromPath(environmentId: string, cwd: string): string { return `${environmentId}:${normalizeProjectPathForComparison(cwd)}`; } export function derivePhysicalProjectKey( project: Pick, ): string { return derivePhysicalProjectKeyFromPath(project.environmentId, project.workspaceRoot); } export function deriveProjectGroupingOverrideKey( project: Pick, ): string { return derivePhysicalProjectKey(project); } export function getProjectOrderKey( project: Pick, ): string { return derivePhysicalProjectKey(project); } export function resolveProjectGroupingMode( project: Pick, settings: ProjectGroupingSettings, ): SidebarProjectGroupingMode { return ( settings.sidebarProjectGroupingOverrides?.[deriveProjectGroupingOverrideKey(project)] ?? settings.sidebarProjectGroupingMode ); } function deriveRepositoryScopedKey( project: Pick, groupingMode: SidebarProjectGroupingMode, ): string | null { const canonicalKey = project.repositoryIdentity?.canonicalKey; if (!canonicalKey) { return null; } if (groupingMode === "repository") { return canonicalKey; } const relativeProjectPath = deriveRepositoryRelativeProjectPath(project); if (relativeProjectPath === null) { return canonicalKey; } return relativeProjectPath.length === 0 ? canonicalKey : `${canonicalKey}::${relativeProjectPath}`; } export function deriveLogicalProjectKey( project: Pick< EnvironmentProject, "environmentId" | "id" | "workspaceRoot" | "repositoryIdentity" >, options?: { readonly groupingMode?: SidebarProjectGroupingMode; }, ): string { const groupingMode = options?.groupingMode ?? "repository"; if (groupingMode === "separate") { return derivePhysicalProjectKey(project); } return ( deriveRepositoryScopedKey(project, groupingMode) ?? derivePhysicalProjectKey(project) ?? scopedProjectKey(scopeProjectRef(project.environmentId, project.id)) ); } export function deriveLogicalProjectKeyFromSettings( project: Pick< EnvironmentProject, "environmentId" | "id" | "workspaceRoot" | "repositoryIdentity" >, settings: ProjectGroupingSettings, ): string { return deriveLogicalProjectKey(project, { groupingMode: resolveProjectGroupingMode(project, settings), }); } export function deriveLogicalProjectKeyFromRef( projectRef: ScopedProjectRef, project: | Pick | null | undefined, options?: { readonly groupingMode?: SidebarProjectGroupingMode; }, ): string { return project ? deriveLogicalProjectKey(project, options) : scopedProjectKey(projectRef); } export function deriveProjectGroupLabel(input: { readonly representative: Pick; readonly members: ReadonlyArray>; }): string { const sharedDisplayNames = uniqueNonEmptyValues( input.members.map((member) => member.repositoryIdentity?.displayName), ); if (sharedDisplayNames.length === 1) { return sharedDisplayNames[0]!; } const sharedRepositoryNames = uniqueNonEmptyValues( input.members.map((member) => member.repositoryIdentity?.name), ); if (sharedRepositoryNames.length === 1) { return sharedRepositoryNames[0]!; } return input.representative.title; }