[CmdletBinding()]
param(
  [ValidateSet('All', 'ScriptoriumAI', 'HyperTwist', 'VectorShell')]
  [string]$Product = 'All',
  [string]$WorkspaceBase = 'C:\Workspaces',
  [switch]$ForceManifestRefresh
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function New-StableGuid {
  param([Parameter(Mandatory = $true)][string]$InputString)

  $bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString.ToLowerInvariant())
  $md5 = [System.Security.Cryptography.MD5]::Create()
  try {
    $hash = $md5.ComputeHash($bytes)
  } finally {
    $md5.Dispose()
  }

  $hex = ($hash | ForEach-Object { $_.ToString('x2') }) -join ''
  $guidText = '{0}-{1}-{2}-{3}-{4}' -f $hex.Substring(0, 8), $hex.Substring(8, 4), $hex.Substring(12, 4), $hex.Substring(16, 4), $hex.Substring(20, 12)
  return "{${guidText}}".ToUpperInvariant()
}

function Ensure-Directory {
  param([Parameter(Mandatory = $true)][string]$Path)

  if (-not (Test-Path -LiteralPath $Path)) {
    New-Item -ItemType Directory -Path $Path | Out-Null
  }
}

function Write-Utf8File {
  param(
    [Parameter(Mandatory = $true)][string]$Path,
    [Parameter(Mandatory = $true)][AllowEmptyString()][string[]]$Lines
  )

  $parent = Split-Path -Path $Path -Parent
  if ($parent) {
    Ensure-Directory -Path $parent
  }

  [System.IO.File]::WriteAllLines($Path, $Lines, [System.Text.UTF8Encoding]::new($false))
}

function New-RelativePath {
  param(
    [Parameter(Mandatory = $true)][string]$BasePath,
    [Parameter(Mandatory = $true)][string]$TargetPath
  )

  $baseResolved = (Resolve-Path -LiteralPath $BasePath).Path.TrimEnd('\') + '\'
  $targetResolved = (Resolve-Path -LiteralPath $TargetPath).Path
  $baseUri = [System.Uri]$baseResolved
  $targetUri = [System.Uri]$targetResolved
  $relativeUri = $baseUri.MakeRelativeUri($targetUri)
  return [System.Uri]::UnescapeDataString($relativeUri.ToString()).Replace('/', '\')
}

function New-MirrorPath {
  param(
    [Parameter(Mandatory = $true)][string]$MirrorRoot,
    [Parameter(Mandatory = $true)][string]$Owner,
    [Parameter(Mandatory = $true)][string]$Repo
  )

  return Join-Path (Join-Path $MirrorRoot $Owner) $Repo
}

function New-RepositoryEntry {
  param(
    [Parameter(Mandatory = $true)][string]$Key,
    [Parameter(Mandatory = $true)][string]$Name,
    [string]$RepoUrl,
    [Parameter(Mandatory = $true)][string]$LocalPath,
    [Parameter(Mandatory = $true)][string]$Purpose,
    [Parameter(Mandatory = $true)][string]$Language,
    [Parameter(Mandatory = $true)][string]$IncludeMode,
    [Parameter(Mandatory = $true)][string]$Storage,
    [bool]$Active = $true,
    [object[]]$ProjectFiles = @(),
    [string]$License = '',
    [string]$LicenseStatus = '',
    [string]$MirrorClass = '',
    [string]$WorkflowRole = '',
    [bool]$ModelAAllowedSourceAccess = $true,
    [bool]$ModelBAllowedSourceAccess = $true,
    [string]$HandoffDoc = '',
    [string[]]$Notes = @()
  )

  return [ordered]@{
    key = $Key
    name = $Name
    repoUrl = $RepoUrl
    localPath = $LocalPath
    purpose = $Purpose
    language = $Language
    includeMode = $IncludeMode
    storage = $Storage
    active = $Active
    projectFiles = $ProjectFiles
    license = $License
    licenseStatus = $LicenseStatus
    mirrorClass = $MirrorClass
    workflowRole = $WorkflowRole
    modelAAllowedSourceAccess = $ModelAAllowedSourceAccess
    modelBAllowedSourceAccess = $ModelBAllowedSourceAccess
    handoffDoc = $HandoffDoc
    notes = $Notes
  }
}

function New-ExternalMirrorEntry {
  param(
    [Parameter(Mandatory = $true)][string]$Key,
    [Parameter(Mandatory = $true)][string]$Name,
    [Parameter(Mandatory = $true)][string]$RepoUrl,
    [Parameter(Mandatory = $true)][string]$MirrorRoot,
    [Parameter(Mandatory = $true)][string]$Owner,
    [Parameter(Mandatory = $true)][string]$Repo,
    [Parameter(Mandatory = $true)][string]$Purpose,
    [Parameter(Mandatory = $true)][string]$Language,
    [Parameter(Mandatory = $true)][string]$License,
    [Parameter(Mandatory = $true)][string]$LicenseStatus,
    [Parameter(Mandatory = $true)][string]$MirrorClass,
    [Parameter(Mandatory = $true)][string]$WorkflowRole,
    [string]$HandoffDoc = '',
    [string[]]$Notes = @(),
    [bool]$Active = $true
  )

  $localPath = New-MirrorPath -MirrorRoot $MirrorRoot -Owner $Owner -Repo $Repo
  $modelBAllowed = $true
  if ($MirrorClass -eq 'restrictive') {
    $modelBAllowed = $false
  }

  return New-RepositoryEntry -Key $Key -Name $Name -RepoUrl $RepoUrl -LocalPath $localPath -Purpose $Purpose -Language $Language -IncludeMode 'root' -Storage 'external-mirror' -Active:$Active -ProjectFiles @() -License $License -LicenseStatus $LicenseStatus -MirrorClass $MirrorClass -WorkflowRole $WorkflowRole -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $modelBAllowed -HandoffDoc $HandoffDoc -Notes $Notes
}

function Get-HyperTwistMirrorEntries {
  param(
    [Parameter(Mandatory = $true)][string]$PermissiveMirrorsRoot,
    [Parameter(Mandatory = $true)][string]$RestrictiveMirrorsRoot,
    [Parameter(Mandatory = $true)][string]$CleanRoomSpecsRoot
  )

  $entries = [System.Collections.Generic.List[object]]::new()

  $entries.Add((New-ExternalMirrorEntry -Key 'hactarce-hyperspeedcube' -Name 'HactarCE - Hyperspeedcube' -RepoUrl 'https://github.com/HactarCE/Hyperspeedcube.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'HactarCE' -Repo 'Hyperspeedcube' -Purpose 'Runtime-side hypercubing foundation anchor and engine donor.' -Language 'Rust' -License 'MIT OR Apache-2.0' -LicenseStatus 'dual-permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'cubing-cubing-js' -Name 'cubing - cubing.js' -RepoUrl 'https://github.com/cubing/cubing.js.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cubing' -Repo 'cubing.js' -Purpose 'Canonical classic-cubing semantics, rendering, scramble/search, and smartcube donor consumed through bounded MPL-aware dependency seams.' -Language 'TypeScript' -License 'MPL-2.0 OR GPL-3.0-or-later' -LicenseStatus 'dual-weak-copyleft-library-use-ok' -MirrorClass 'permissive' -WorkflowRole 'boundary-sensitive-donor' -Notes @('Preferred use is package dependency, adapter boundary, or bounded companion surface rather than a deep private fork.', 'If HyperTwist modifies cubing.js source files directly, those modifications must be published.', 'Preserve vendored-code and third-party notices documented by the upstream repo.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'kkoomen-qbr' -Name 'kkoomen - qbr' -RepoUrl 'https://github.com/kkoomen/qbr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'kkoomen' -Repo 'qbr' -Purpose 'Primary classic-cube live recognition and calibration donor.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'vivaansinghvi07-rubix-cube-solver' -Name 'vivaansinghvi07 - rubix-cube-solver' -RepoUrl 'https://github.com/vivaansinghvi07/rubix-cube-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'vivaansinghvi07' -Repo 'rubix-cube-solver' -Purpose 'Secondary browser/video reconstruction companion for the vision stack.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'parallel-foundation-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'cahidenes-rubiks-cube-solver' -Name 'cahidenes - rubiks-cube-solver' -RepoUrl 'https://github.com/cahidenes/rubiks-cube-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cahidenes' -Repo 'rubiks-cube-solver' -Purpose 'Recognition heuristics and solver-bridge donor.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'tentone-rubix-solver' -Name 'tentone - rubix-solver' -RepoUrl 'https://github.com/tentone/rubix-solver.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'tentone' -Repo 'rubix-solver' -Purpose 'C++ and OpenCV perception-heuristics donor.' -Language 'C++' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'yakupbilen-drl-rubiks-cube' -Name 'yakupbilen - drl-rubiks-cube' -RepoUrl 'https://github.com/yakupbilen/drl-rubiks-cube.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'yakupbilen' -Repo 'drl-rubiks-cube' -Purpose 'RL and search experimentation bench for the coaching stack.' -Language 'Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'training-bench'))
  $entries.Add((New-ExternalMirrorEntry -Key 'brianpeiris-riftsketch' -Name 'brianpeiris - RiftSketch' -RepoUrl 'https://github.com/brianpeiris/RiftSketch.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'brianpeiris' -Repo 'RiftSketch' -Purpose 'XR interaction reference and immersion donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'reference-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'nuilab-code-vr' -Name 'NuiLab - code-vr' -RepoUrl 'https://github.com/NuiLab/code-vr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'NuiLab' -Repo 'code-vr' -Purpose 'XR pedagogy and onboarding donor.' -Language 'Rust' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'reference-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'hypercubers-hypercubing-xyz' -Name 'Hypercubers - hypercubing.xyz' -RepoUrl 'https://github.com/Hypercubers/hypercubing.xyz.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Hypercubers' -Repo 'hypercubing.xyz' -Purpose 'Knowledge, curriculum, notation, and community donor for the hypercubing half of HyperTwist.' -Language 'Markdown/Python' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'knowledge-foundation-donor' -Notes @('Use the GitHub source repo as canonical for active evaluation.', 'The local downloaded C:\\HyperTwist\\Hypercubing.xyz snapshot is intentionally excluded from active evaluation for now.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magictile' -Name 'roice3 - MagicTile' -RepoUrl 'https://github.com/roice3/MagicTile.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'MagicTile' -Purpose 'Top-tier non-Euclidean geometry, topology, and generalized puzzle-construction donor for the hypercubing expansion lane.' -Language 'C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a geometry/topology donor under the Hyperspeedcube anchor, not as the main product shell.', 'The underlying R3 tiling and topology layers are the primary salvage target, not the WinForms/OpenTK shell.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'cutelyaware-magiccube4d' -Name 'cutelyaware - magiccube4d' -RepoUrl 'https://github.com/cutelyaware/magiccube4d.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'cutelyaware' -Repo 'magiccube4d' -Purpose 'Legacy 4D hypercubing interaction, history, macro, and generic puzzle-description donor with explicit attribution and notice obligations.' -Language 'Java' -License 'Custom permissive attribution-request license' -LicenseStatus 'custom-permissive-attribution' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Preserve upstream attribution in natural product-facing places such as About dialogs, source comments, or third-party notices, and include the project link http://superliminal.com/cube/cube.htm.', 'Primary salvage targets are 4D interaction grammar, history and macro semantics, generic puzzle boundaries, and proven legacy hypercubing workflow.', 'Track the provenance note in com\\donhatchsw\\util\\MyMath.java in project license records, but do not treat it as a standing blocker.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magic120cell' -Name 'roice3 - Magic120Cell' -RepoUrl 'https://github.com/roice3/Magic120Cell.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'Magic120Cell' -Purpose 'Specialized 4D interaction, visibility, and puzzle-specific UX donor for the hypercubing expansion family.' -Language 'C++/C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a specialized descendant in the MagicTile family, not as the main runtime or geometry foundation.', 'Primary salvage targets are 4D interaction grammar, visibility modes, twist-history UX, and advanced teaching-surface behavior.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'roice3-magiccube5d' -Name 'roice3 - MagicCube5D' -RepoUrl 'https://github.com/roice3/MagicCube5D.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'roice3' -Repo 'MagicCube5D' -Purpose 'Specialized 5D cube-family interaction, progress, macro, and teaching-surface donor.' -Language 'C++/C#' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor' -Notes @('Treat as a specialized cube-family donor below Hyperspeedcube and MagicTile, not as a product-shell foundation.', 'Primary salvage targets are 5D interaction grammar, macros, solved-progress reporting, sticker/cubie finding, slice handling, and advanced teaching-surface behavior.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'poliva-cubedex' -Name 'poliva - cubedex' -RepoUrl 'https://github.com/poliva/cubedex.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'poliva' -Repo 'cubedex' -Purpose 'Smartcube-aware training UX and offline-first donor.' -Language 'TypeScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'tao-yu-alg-trainer' -Name 'tao-yu - Alg-Trainer' -RepoUrl 'https://github.com/tao-yu/Alg-Trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'tao-yu' -Repo 'Alg-Trainer' -Purpose 'Conceptual training-lane foundation candidate and heavy donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'foundation-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'lykos-cube-trainer' -Name 'Lykos - cube_trainer' -RepoUrl 'https://github.com/Lykos/cube_trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Lykos' -Repo 'cube_trainer' -Purpose 'Persistent training-domain and coaching-backend donor.' -Language 'Ruby' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'strategic-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'newyork-anthonyng-rubiks-cross-trainer' -Name 'newyork-anthonyng - rubiks-cross-trainer' -RepoUrl 'https://github.com/newyork-anthonyng/rubiks-cross-trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'newyork-anthonyng' -Repo 'rubiks-cross-trainer' -Purpose 'Focused CFOP cross-drill donor.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'abunickabhi-5style-trainer' -Name 'abunickabhi - 5style-Trainer' -RepoUrl 'https://github.com/abunickabhi/5style-Trainer.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'abunickabhi' -Repo '5style-Trainer' -Purpose 'Focused advanced-training donor for 5-style and BLD practice.' -Language 'JavaScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'aarav2709-kubetimr' -Name 'Aarav2709 - KubeTimr' -RepoUrl 'https://github.com/Aarav2709/KubeTimr.git' -MirrorRoot $PermissiveMirrorsRoot -Owner 'Aarav2709' -Repo 'KubeTimr' -Purpose 'Focused timer, splits, stats, and local-practice donor.' -Language 'TypeScript' -License 'MIT' -LicenseStatus 'permissive' -MirrorClass 'permissive' -WorkflowRole 'focused-donor'))
  $entries.Add((New-ExternalMirrorEntry -Key 'amontesl-codexr' -Name 'aMonteSl - CodeXR' -RepoUrl 'https://github.com/aMonteSl/CodeXR.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'aMonteSl' -Repo 'CodeXR' -Purpose 'Off-domain XR reference under strong copyleft; keep only as restrictive reference material.' -Language 'TypeScript' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'reference-only' -Notes @('Model B must not read this mirror.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'avikaufman-rubix-cube-trainer' -Name 'AviKaufman - Rubix-cube-trainer' -RepoUrl 'https://github.com/AviKaufman/Rubix-cube-trainer.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'AviKaufman' -Repo 'Rubix-cube-trainer' -Purpose 'Clean-room pedagogy benchmark for beginner-method lesson flow.' -Language 'TypeScript' -License 'All rights reserved' -LicenseStatus 'all-rights-reserved' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'avi-kaufman-rubix-cube-trainer.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'ambisinister-blindsolve' -Name 'ambisinister - blindsolve' -RepoUrl 'https://github.com/ambisinister/blindsolve.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'ambisinister' -Repo 'blindsolve' -Purpose 'Clean-room BLD memo benchmark for hide-reveal and letter-pair rehearsal flow.' -Language 'Python' -License 'No explicit license found' -LicenseStatus 'license-missing' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'ambisinister-blindsolve.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'alinen-cube' -Name 'alinen - cube' -RepoUrl 'https://github.com/alinen/cube.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'alinen' -Repo 'cube' -Purpose 'Clean-room planner benchmark for modular state, task, and step-planner behavior.' -Language 'C#' -License 'No explicit license found' -LicenseStatus 'license-missing' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'alinen-cube.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'brownan-rubiks-cube-solver' -Name 'brownan - Rubiks-Cube-Solver' -RepoUrl 'https://github.com/brownan/Rubiks-Cube-Solver.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'brownan' -Repo 'Rubiks-Cube-Solver' -Purpose 'Primary benchmark oracle and approved clean-room target for a future first-party optimal-solver lane.' -Language 'C' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'brownan-rubiks-cube-solver.model-a.md') -Notes @('Retain this repo as a correctness and optimality oracle even if a first-party solver is later implemented.', 'Model B must not read this mirror.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'mathewkj2048-rubiks-cube-simulator' -Name 'MathewKJ2048 - Rubiks-cube-simulator' -RepoUrl 'https://github.com/MathewKJ2048/Rubiks-cube-simulator.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'MathewKJ2048' -Repo 'Rubiks-cube-simulator' -Purpose 'Low-priority clean-room simulator benchmark for lightweight move grammar, history, and timed playback behavior.' -Language 'Java' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'mathewkj2048-rubiks-cube-simulator.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.')))
  $entries.Add((New-ExternalMirrorEntry -Key 'vwcwong-cubesim' -Name 'vwcwong - CubeSim' -RepoUrl 'https://github.com/vwcwong/CubeSim.git' -MirrorRoot $RestrictiveMirrorsRoot -Owner 'vwcwong' -Repo 'CubeSim' -Purpose 'Clean-room state/history/simulator benchmark for renderer-independent cube-domain design.' -Language 'Python' -License 'GPL-3.0' -LicenseStatus 'strong-copyleft' -MirrorClass 'restrictive' -WorkflowRole 'clean-room-candidate' -HandoffDoc (Join-Path $CleanRoomSpecsRoot 'vwcwong-cubesim.model-a.md') -Notes @('Model B must not read this mirror.', 'Use only the scrubbed Model A dossier and acceptance tests for later implementation.')))

  return $entries
}

function Get-ProductConfig {
  param(
    [Parameter(Mandatory = $true)][ValidateSet('ScriptoriumAI', 'HyperTwist', 'VectorShell')][string]$Name,
    [Parameter(Mandatory = $true)][string]$WorkspaceBasePath
  )

  $workspaceRoot = Join-Path $WorkspaceBasePath $Name
  $mirrorsRoot = Join-Path $workspaceRoot 'mirrors'
  $permissiveMirrorsRoot = Join-Path $mirrorsRoot 'permissive'
  $restrictiveMirrorsRoot = Join-Path $mirrorsRoot 'restrictive'
  $cleanRoomSpecsRoot = Join-Path $workspaceRoot 'clean-room-specs'
  $implementationWorkspacesRoot = Join-Path $workspaceRoot 'implementation-workspaces'
  $modelBWorkspacesRoot = Join-Path $implementationWorkspacesRoot 'model-b'
  $scratchWorkspacesRoot = Join-Path $implementationWorkspacesRoot 'scratch'
  $generatedRoot = Join-Path $workspaceRoot 'generated'
  $cacheRoot = Join-Path $workspaceRoot 'cache'
  $plannedAppRoot = Join-Path $workspaceRoot 'app'

  switch ($Name) {
    'ScriptoriumAI' {
      return [ordered]@{
        Name = $Name
        RepoRoot = 'C:\ScriptoriumAI'
        DocsRoot = 'C:\ScriptoriumAI\docs'
        WorkspaceRoot = $workspaceRoot
        PlannedAppRoot = $plannedAppRoot
        MirrorsRoot = $mirrorsRoot
        PermissiveMirrorsRoot = $permissiveMirrorsRoot
        RestrictiveMirrorsRoot = $restrictiveMirrorsRoot
        CleanRoomSpecsRoot = $cleanRoomSpecsRoot
        ImplementationWorkspacesRoot = $implementationWorkspacesRoot
        ModelBWorkspacesRoot = $modelBWorkspacesRoot
        ScratchWorkspacesRoot = $scratchWorkspacesRoot
        GeneratedRoot = $generatedRoot
        CacheRoot = $cacheRoot
        RepositoryEntries = @(
          (New-RepositoryEntry -Key 'app' -Name 'ScriptoriumAI App' -RepoUrl '' -LocalPath 'C:\ScriptoriumAI' -Purpose 'First-party product repository.' -Language 'mixed' -IncludeMode 'project' -Storage 'app-repo' -ProjectFiles @(
            [ordered]@{ name = 'ScriptoriumAI'; path = 'C:\ScriptoriumAI\ScriptoriumAI.vcxproj'; kind = 'cpp' },
            [ordered]@{ name = 'scriptoriumai-ui'; path = 'C:\ScriptoriumAI\scriptoriumai-ui\scriptoriumai-ui.esproj'; kind = 'js' },
            [ordered]@{ name = 'ScriptoriumAI-Server'; path = 'C:\ScriptoriumAI\ScriptoriumAI-Server\ScriptoriumAI-Server.esproj'; kind = 'js' }
          ) -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true),
          (New-RepositoryEntry -Key 'gitnexus' -Name 'GitNexus' -RepoUrl 'https://github.com/abhigyanpatwari/GitNexus.git' -LocalPath 'C:\ScriptoriumAI\mirrors\GitNexus' -Purpose 'Pinned internal code-intelligence tooling retained as an in-repo submodule.' -Language 'TypeScript' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'claude-skills' -Name 'claude-skills' -RepoUrl 'https://github.com/alirezarezvani/claude-skills.git' -LocalPath 'C:\ScriptoriumAI\mirrors\claude-skills' -Purpose 'Pinned skills corpus used by the skills-management flow.' -Language 'Markdown' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'awesome-claude-skills' -Name 'awesome-claude-skills' -RepoUrl 'https://github.com/ComposioHQ/awesome-claude-skills.git' -LocalPath 'C:\ScriptoriumAI\mirrors\awesome-claude-skills' -Purpose 'Pinned scout source for skill ingestion.' -Language 'Markdown' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'latexocr' -Name 'latexocr' -RepoUrl 'https://github.com/lukas-blecher/LaTeX-OCR.git' -LocalPath 'C:\ScriptoriumAI\mirrors\latexocr' -Purpose 'OCR runtime used by equation-to-LaTeX services.' -Language 'Python' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'pdfdelta' -Name 'pdfdelta' -RepoUrl 'https://github.com/balajisivaraman/pdfdelta.git' -LocalPath 'C:\ScriptoriumAI\mirrors\pdfdelta' -Purpose 'PDF differencing assets and examples referenced by runboard flows.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'pdfmd' -Name 'pdfmd' -RepoUrl 'https://github.com/reez/pdfmd.git' -LocalPath 'C:\ScriptoriumAI\mirrors\pdfmd' -Purpose 'PDF-to-Markdown tooling kept pinned for intake and tests.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'code2video' -Name 'code2video' -RepoUrl 'https://github.com/itsjavi/code2video.git' -LocalPath 'C:\ScriptoriumAI\mirrors\code2video' -Purpose 'Code-to-video transform engine referenced by server services.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'paper2video' -Name 'paper2video' -RepoUrl 'https://github.com/harish-03/paper2video.git' -LocalPath 'C:\ScriptoriumAI\mirrors\paper2video' -Purpose 'Paper-to-video transform engine referenced by server services.' -Language 'mixed' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency'),
          (New-RepositoryEntry -Key 'liquid-glass-react' -Name 'liquid-glass-react' -RepoUrl 'https://github.com/rdev/liquid-glass-react.git' -LocalPath 'C:\ScriptoriumAI\mirrors\liquid-glass-react' -Purpose 'Pinned visual donor for the glass UI implementation.' -Language 'TypeScript' -IncludeMode 'root' -Storage 'submodule' -License 'unknown' -LicenseStatus 'pending' -MirrorClass 'in-repo' -WorkflowRole 'pinned-dependency')
        )
      }
    }
    'HyperTwist' {
      $entries = [System.Collections.Generic.List[object]]::new()
      $entries.Add((New-RepositoryEntry -Key 'app' -Name 'HyperTwist App' -RepoUrl '' -LocalPath 'C:\HyperTwist' -Purpose 'First-party product repository.' -Language 'mixed' -IncludeMode 'project' -Storage 'app-repo' -ProjectFiles @(
        [ordered]@{ name = 'HyperTwist'; path = 'C:\HyperTwist\HyperTwist.vcxproj'; kind = 'cpp' }
      ) -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true))

      foreach ($entry in (Get-HyperTwistMirrorEntries -PermissiveMirrorsRoot $permissiveMirrorsRoot -RestrictiveMirrorsRoot $restrictiveMirrorsRoot -CleanRoomSpecsRoot $cleanRoomSpecsRoot)) {
        $entries.Add($entry)
      }

      return [ordered]@{
        Name = $Name
        RepoRoot = 'C:\HyperTwist'
        DocsRoot = 'C:\HyperTwist\docs'
        WorkspaceRoot = $workspaceRoot
        PlannedAppRoot = $plannedAppRoot
        MirrorsRoot = $mirrorsRoot
        PermissiveMirrorsRoot = $permissiveMirrorsRoot
        RestrictiveMirrorsRoot = $restrictiveMirrorsRoot
        CleanRoomSpecsRoot = $cleanRoomSpecsRoot
        ImplementationWorkspacesRoot = $implementationWorkspacesRoot
        ModelBWorkspacesRoot = $modelBWorkspacesRoot
        ScratchWorkspacesRoot = $scratchWorkspacesRoot
        GeneratedRoot = $generatedRoot
        CacheRoot = $cacheRoot
        RepositoryEntries = @($entries)
      }
    }
    'VectorShell' {
      return [ordered]@{
        Name = $Name
        RepoRoot = 'C:\VectorShell'
        DocsRoot = 'C:\VectorShell\docs'
        WorkspaceRoot = $workspaceRoot
        PlannedAppRoot = $plannedAppRoot
        MirrorsRoot = $mirrorsRoot
        PermissiveMirrorsRoot = $permissiveMirrorsRoot
        RestrictiveMirrorsRoot = $restrictiveMirrorsRoot
        CleanRoomSpecsRoot = $cleanRoomSpecsRoot
        ImplementationWorkspacesRoot = $implementationWorkspacesRoot
        ModelBWorkspacesRoot = $modelBWorkspacesRoot
        ScratchWorkspacesRoot = $scratchWorkspacesRoot
        GeneratedRoot = $generatedRoot
        CacheRoot = $cacheRoot
        RepositoryEntries = @(
          (New-RepositoryEntry -Key 'app' -Name 'VectorShell App' -RepoUrl '' -LocalPath 'C:\VectorShell' -Purpose 'First-party product repository.' -Language 'mixed' -IncludeMode 'project' -Storage 'app-repo' -ProjectFiles @(
            [ordered]@{ name = 'VectorShell'; path = 'C:\VectorShell\VectorShell.vcxproj'; kind = 'cpp' }
          ) -License 'first-party' -LicenseStatus 'first-party' -MirrorClass 'app-repo' -WorkflowRole 'first-party' -ModelAAllowedSourceAccess $true -ModelBAllowedSourceAccess $true)
        )
      }
    }
  }
}

function New-ManifestObject {
  param([Parameter(Mandatory = $true)][hashtable]$Config)

  return [ordered]@{
    schemaVersion = 2
    productName = $Config.Name
    generatedAtUtc = (Get-Date).ToUniversalTime().ToString('o')
    workspace = [ordered]@{
      workspaceRoot = $Config.WorkspaceRoot
      currentAppRoot = $Config.RepoRoot
      plannedAppRoot = $Config.PlannedAppRoot
      mirrorsRoot = $Config.MirrorsRoot
      permissiveMirrorsRoot = $Config.PermissiveMirrorsRoot
      restrictiveMirrorsRoot = $Config.RestrictiveMirrorsRoot
      cleanRoomSpecsRoot = $Config.CleanRoomSpecsRoot
      implementationWorkspacesRoot = $Config.ImplementationWorkspacesRoot
      modelBWorkspacesRoot = $Config.ModelBWorkspacesRoot
      scratchWorkspacesRoot = $Config.ScratchWorkspacesRoot
      generatedRoot = $Config.GeneratedRoot
      cacheRoot = $Config.CacheRoot
    }
    policy = [ordered]@{
      defaultIncludeMode = 'root'
      promoteToProjectWhenEditing = $true
      shippingSource = 'currentAppRoot'
      moveAppIntoWorkspaceDeferred = $true
      restrictiveMirrorsModelAOnly = $true
      modelBMayReadRestrictiveMirrors = $false
      benchmarkOracleDefaultUsage = 'correctness-reference-and-test-oracle'
      cleanRoomDefaultUsage = 'model-a-spec-then-isolated-model-b-implementation'
      notes = @(
        'The live application repository remains at currentAppRoot for now.',
        'plannedAppRoot exists as the future migration target once a deliberate move is approved.',
        'Use storage=submodule only for repos that must remain pinned inside the product repo.',
        'Use storage=external-mirror for large intake queues and promote to project mode only when actively editing.',
        'Restrictive mirrors are for Model A research custody only. Model B must consume scrubbed handoff artifacts instead of source.',
        'Benchmark oracles may remain restrictive. Their main value is correctness, regression, and quality comparison rather than code reuse.'
      )
    }
    repositories = $Config.RepositoryEntries
  }
}

function Write-WorkspaceReadmes {
  param([Parameter(Mandatory = $true)][hashtable]$Config)

  Write-Utf8File -Path (Join-Path $Config.WorkspaceRoot 'README.md') -Lines @(
    "# $($Config.Name) external workspace",
    '',
    "This workspace is the external mirror and handoff control plane for $($Config.Name).",
    '',
    "Current live repo: $($Config.RepoRoot)",
    "Planned future app folder: $($Config.PlannedAppRoot)",
    "Permissive mirrors root: $($Config.PermissiveMirrorsRoot)",
    "Restrictive mirrors root: $($Config.RestrictiveMirrorsRoot)",
    "Clean-room specs root: $($Config.CleanRoomSpecsRoot)",
    "Implementation workspaces root: $($Config.ImplementationWorkspacesRoot)",
    '',
    'Operating rules:',
    '- Keep first-party shipping code in the live app repo until an explicit migration is approved.',
    '- Place permissive donor repos under mirrors\permissive.',
    '- Place GPL, no-license, and all-rights-reserved repos under mirrors\restrictive.',
    '- Treat clean-room-specs as the safe handoff zone from Model A to Model B.',
    '- Treat implementation-workspaces\model-b as the isolated implementation surface that must not read restrictive mirrors.'
  )

  Write-Utf8File -Path (Join-Path $Config.PlannedAppRoot 'README.md') -Lines @(
    "# $($Config.Name) app placeholder",
    '',
    "This folder is reserved for a future migration of the live repo into the external workspace layout.",
    '',
    "Current live repo path: $($Config.RepoRoot)",
    '',
    'Do not move the repository here implicitly. The migration should be a separate, deliberate step.'
  )

  Write-Utf8File -Path (Join-Path $Config.MirrorsRoot 'README.md') -Lines @(
    "# $($Config.Name) mirrors",
    '',
    'This folder is split by exposure and licensing posture:',
    '- permissive: MIT, Apache, BSD, or otherwise acceptable direct donor mirrors',
    '- restrictive: GPL, no-license, all-rights-reserved, or otherwise source-restricted mirrors',
    '',
    'Mirror rules:',
    '- Mirror repos by owner\repo to avoid collisions.',
    '- Keep manifest metadata current; do not treat ad hoc clone folders as authoritative.',
    '- Restrictive mirrors are Model A only.'
  )

  Write-Utf8File -Path (Join-Path $Config.PermissiveMirrorsRoot 'README.md') -Lines @(
    "# $($Config.Name) permissive mirrors",
    '',
    'Use this root for repos whose source may be read during normal donor evaluation and, when appropriate, direct integration work.',
    '',
    'Default policy:',
    '- includeMode = root',
    '- storage = external-mirror',
    '- Model A source access = allowed',
    '- Model B source access = allowed if the repo remains in the permissive donor lane'
  )

  Write-Utf8File -Path (Join-Path $Config.RestrictiveMirrorsRoot 'README.md') -Lines @(
    "# $($Config.Name) restrictive mirrors",
    '',
    'Use this root for GPL, no-license, all-rights-reserved, and otherwise source-restricted mirrors.',
    '',
    'Critical policy:',
    '- Model A source access = allowed for research, benchmark-oracle work, and clean-room specification work',
    '- Model B source access = forbidden',
    '- Later implementation must consume only scrubbed handoff artifacts from clean-room-specs'
  )

  Write-Utf8File -Path (Join-Path $Config.CleanRoomSpecsRoot 'README.md') -Lines @(
    "# $($Config.Name) clean-room specs",
    '',
    'This root is the safe handoff zone for scrubbed Model A outputs.',
    '',
    'Expected contents:',
    '- behavior specifications',
    '- architecture notes at a black-box level',
    '- acceptance tests',
    '- no-copy guardrails',
    '- benchmark-oracle usage notes when a restrictive repo is kept only as a correctness reference',
    '',
    'Recommended naming pattern:',
    '- <owner>-<repo>.model-a.md for repo-specific clean-room handoff files',
    '- <owner>-<repo>.oracle.md for restrictive oracle guidance files',
    '- benchmark-oracle-vs-clean-room.md for the general distinction guide',
    '- model-a-model-b-prompts.md for reusable role prompts'
  )

  Write-Utf8File -Path (Join-Path $Config.ImplementationWorkspacesRoot 'README.md') -Lines @(
    "# $($Config.Name) implementation workspaces",
    '',
    'This root is for isolated build and implementation work that consumes only approved inputs.',
    '',
    'Expected subfolders:',
    '- model-b: isolated clean-room implementation workspace',
    '- scratch: safe local experimentation that does not read restrictive mirrors'
  )

  Write-Utf8File -Path (Join-Path $Config.ModelBWorkspacesRoot 'README.md') -Lines @(
    "# $($Config.Name) model-b workspace",
    '',
    'This directory is reserved for isolated clean-room implementation work.',
    '',
    'Rules:',
    '- do not clone or mount restrictive mirrors here',
    '- consume only scrubbed specs, tests, and handoff notes from clean-room-specs',
    '- if a session has already read restrictive source, do not use that session here'
  )

  Write-Utf8File -Path (Join-Path $Config.ScratchWorkspacesRoot 'README.md') -Lines @(
    "# $($Config.Name) scratch workspace",
    '',
    'Use this directory for safe local experiments, temporary validation harnesses, and non-authoritative test projects.',
    '',
    'Do not treat this folder as the long-term source of truth.'
  )

  Write-Utf8File -Path (Join-Path $Config.GeneratedRoot 'README.md') -Lines @(
    "# $($Config.Name) generated artifacts",
    '',
    'This directory is generated from repos.manifest.json.',
    '',
    'Artifacts:',
    '- main.sln: first-party projects plus active pinned integrations',
    '- catalog.sln: lightweight catalog view of mirrors, specs, and pinned dependencies',
    '- active.slnf: current working-set filter'
  )

  Write-Utf8File -Path (Join-Path $Config.CacheRoot 'README.md') -Lines @(
    "# $($Config.Name) cache",
    '',
    'Use this directory for clone cache, bare mirrors, temporary sync state, or generated lookup tables that should not live in the live app repo.'
  )
}

function Write-WorkspaceGitignore {
  param([Parameter(Mandatory = $true)][hashtable]$Config)

  Write-Utf8File -Path (Join-Path $Config.WorkspaceRoot '.gitignore') -Lines @(
    '.vs/',
    'mirrors/',
    'cache/',
    'logs/',
    'tmp/',
    'implementation-workspaces/model-b/*',
    '!implementation-workspaces/model-b/README.md',
    'implementation-workspaces/scratch/*',
    '!implementation-workspaces/scratch/README.md',
    '*.user',
    '*.suo'
  )
}

function Write-ManifestFile {
  param(
    [Parameter(Mandatory = $true)][hashtable]$Config,
    [Parameter(Mandatory = $true)][bool]$RefreshManifest
  )

  $manifestPath = Join-Path $Config.WorkspaceRoot 'repos.manifest.json'
  if ((-not (Test-Path -LiteralPath $manifestPath)) -or $RefreshManifest) {
    $manifest = New-ManifestObject -Config $Config
    $json = $manifest | ConvertTo-Json -Depth 10
    [System.IO.File]::WriteAllText($manifestPath, $json + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
  }
}

function Get-Manifest {
  param([Parameter(Mandatory = $true)][hashtable]$Config)

  $manifestPath = Join-Path $Config.WorkspaceRoot 'repos.manifest.json'
  return Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
}

function Get-ProjectEntriesFromManifest {
  param([Parameter(Mandatory = $true)]$Manifest)

  $entries = [System.Collections.Generic.List[hashtable]]::new()
  foreach ($repository in $Manifest.repositories) {
    foreach ($projectFile in $repository.projectFiles) {
      $kind = if ($projectFile.kind) { [string]$projectFile.kind } else {
        if ($projectFile.path -like '*.vcxproj') { 'cpp' } else { 'js' }
      }

      $entries.Add([ordered]@{
        Name = [string]$projectFile.name
        Path = [string]$projectFile.path
        Kind = $kind
        Guid = New-StableGuid "project::$($projectFile.path)"
      })
    }
  }

  return $entries
}

function Write-SolutionFile {
  param(
    [Parameter(Mandatory = $true)][string]$SolutionPath,
    [Parameter(Mandatory = $true)][hashtable[]]$ProjectEntries,
    [Parameter(Mandatory = $true)][hashtable[]]$FolderEntries
  )

  $folderProjectTypeGuid = '{2150E333-8FDC-42A3-9474-1A3956D46DE8}'
  $jsProjectTypeGuid = '{54A90642-561A-4BB1-A94E-469ADEE60C69}'
  $cppProjectTypeGuid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'
  $solutionDir = Split-Path -Path $SolutionPath -Parent
  $lines = [System.Collections.Generic.List[string]]::new()

  $lines.Add('')
  $lines.Add('Microsoft Visual Studio Solution File, Format Version 12.00')
  $lines.Add('# Visual Studio Version 17')
  $lines.Add('VisualStudioVersion = 17.14.36307.6')
  $lines.Add('MinimumVisualStudioVersion = 10.0.40219.1')

  foreach ($project in $ProjectEntries) {
    $projectTypeGuid = if ($project.Kind -eq 'js') { $jsProjectTypeGuid } else { $cppProjectTypeGuid }
    $relativeProjectPath = New-RelativePath -BasePath $solutionDir -TargetPath $project.Path
    $lines.Add("Project(`"$projectTypeGuid`") = `"$($project.Name)`", `"$relativeProjectPath`", `"$($project.Guid)`"")
    $lines.Add('EndProject')
  }

  foreach ($folder in $FolderEntries) {
    $folderFiles = @()
    if ($null -ne $folder.Files) {
      $folderFiles = @($folder.Files | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) -and (Test-Path -LiteralPath $_) })
    }

    $lines.Add("Project(`"$folderProjectTypeGuid`") = `"$($folder.Name)`", `"$($folder.Name)`", `"$($folder.Guid)`"")
    if ($folderFiles.Count -gt 0) {
      $lines.Add("`tProjectSection(SolutionItems) = preProject")
      foreach ($filePath in $folderFiles) {
        $relativeFilePath = New-RelativePath -BasePath $solutionDir -TargetPath $filePath
        $lines.Add("`t`t$relativeFilePath = $relativeFilePath")
      }
      $lines.Add("`tEndProjectSection")
    }
    $lines.Add('EndProject')
  }

  $lines.Add('Global')
  $lines.Add("`tGlobalSection(SolutionConfigurationPlatforms) = preSolution")
  $lines.Add("`t`tDebug|x64 = Debug|x64")
  $lines.Add("`t`tRelease|x64 = Release|x64")
  $lines.Add("`tEndGlobalSection")
  $lines.Add("`tGlobalSection(ProjectConfigurationPlatforms) = postSolution")
  foreach ($project in $ProjectEntries) {
    if ($project.Kind -eq 'cpp') {
      $lines.Add("`t`t$($project.Guid).Debug|x64.ActiveCfg = Debug|x64")
      $lines.Add("`t`t$($project.Guid).Debug|x64.Build.0 = Debug|x64")
      $lines.Add("`t`t$($project.Guid).Release|x64.ActiveCfg = Release|x64")
      $lines.Add("`t`t$($project.Guid).Release|x64.Build.0 = Release|x64")
    } else {
      $lines.Add("`t`t$($project.Guid).Debug|x64.ActiveCfg = Debug|Any CPU")
      $lines.Add("`t`t$($project.Guid).Release|x64.ActiveCfg = Release|Any CPU")
    }
  }
  $lines.Add("`tEndGlobalSection")

  $nestedFolders = @($FolderEntries | Where-Object { $null -ne $_.ParentGuid -and $_.ParentGuid -ne '' })
  if ($nestedFolders.Count -gt 0) {
    $lines.Add("`tGlobalSection(NestedProjects) = preSolution")
    foreach ($folder in $nestedFolders) {
      $lines.Add("`t`t$($folder.Guid) = $($folder.ParentGuid)")
    }
    $lines.Add("`tEndGlobalSection")
  }

  $lines.Add("`tGlobalSection(SolutionProperties) = preSolution")
  $lines.Add("`t`tHideSolutionNode = FALSE")
  $lines.Add("`tEndGlobalSection")
  $lines.Add('EndGlobal')

  [System.IO.File]::WriteAllLines($SolutionPath, $lines, [System.Text.UTF8Encoding]::new($false))
}

function Write-WorkspaceArtifacts {
  param(
    [Parameter(Mandatory = $true)][hashtable]$Config,
    [Parameter(Mandatory = $true)]$Manifest
  )

  $projectEntries = Get-ProjectEntriesFromManifest -Manifest $Manifest

  $workspaceConfigFiles = @(
    (Join-Path $Config.WorkspaceRoot 'README.md'),
    (Join-Path $Config.WorkspaceRoot 'repos.manifest.json'),
    (Join-Path $Config.WorkspaceRoot '.gitignore'),
    (Join-Path $Config.PlannedAppRoot 'README.md'),
    (Join-Path $Config.MirrorsRoot 'README.md'),
    (Join-Path $Config.PermissiveMirrorsRoot 'README.md'),
    (Join-Path $Config.RestrictiveMirrorsRoot 'README.md'),
    (Join-Path $Config.CleanRoomSpecsRoot 'README.md'),
    (Join-Path $Config.ImplementationWorkspacesRoot 'README.md'),
    (Join-Path $Config.ModelBWorkspacesRoot 'README.md'),
    (Join-Path $Config.ScratchWorkspacesRoot 'README.md'),
    (Join-Path $Config.GeneratedRoot 'README.md'),
    (Join-Path $Config.CacheRoot 'README.md')
  )

  $workspaceConfigFolder = [ordered]@{
    Name = 'Workspace Config'
    Guid = New-StableGuid "$($Config.Name)::workspace-config"
    ParentGuid = $null
    Files = $workspaceConfigFiles
  }

  $mainFolders = [System.Collections.Generic.List[hashtable]]::new()
  $mainFolders.Add($workspaceConfigFolder)

  $pinnedIntegrations = @($Manifest.repositories | Where-Object { $_.storage -eq 'submodule' -and $_.active })
  if ($pinnedIntegrations.Count -gt 0) {
    $pinnedRootGuid = New-StableGuid "$($Config.Name)::pinned-root"
    $mainFolders.Add([ordered]@{
      Name = 'Pinned Integrations'
      Guid = $pinnedRootGuid
      ParentGuid = $null
      Files = @()
    })

    foreach ($repository in $pinnedIntegrations) {
      $mainFolders.Add([ordered]@{
        Name = [string]$repository.name
        Guid = New-StableGuid "$($Config.Name)::main::$($repository.key)"
        ParentGuid = $pinnedRootGuid
        Files = @()
      })
    }
  }

  Write-SolutionFile -SolutionPath (Join-Path $Config.GeneratedRoot 'main.sln') -ProjectEntries $projectEntries -FolderEntries $mainFolders

  $catalogFolders = [System.Collections.Generic.List[hashtable]]::new()
  $catalogFolders.Add($workspaceConfigFolder)

  $submoduleRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'submodule' })
  $permissiveRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'external-mirror' -and $_.mirrorClass -eq 'permissive' })
  $restrictiveRepos = @($Manifest.repositories | Where-Object { $_.storage -eq 'external-mirror' -and $_.mirrorClass -eq 'restrictive' })
  $cleanRoomDocs = @($Manifest.repositories | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_.handoffDoc) } | ForEach-Object { [string]$_.handoffDoc } | Select-Object -Unique)

  if ($submoduleRepos.Count -gt 0) {
    $submoduleRootGuid = New-StableGuid "$($Config.Name)::catalog::submodules"
    $catalogFolders.Add([ordered]@{
      Name = 'Pinned Submodules'
      Guid = $submoduleRootGuid
      ParentGuid = $null
      Files = @()
    })

    foreach ($repository in $submoduleRepos) {
      $catalogFolders.Add([ordered]@{
        Name = [string]$repository.name
        Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)"
        ParentGuid = $submoduleRootGuid
        Files = @()
      })
    }
  }

  $permissiveRootGuid = New-StableGuid "$($Config.Name)::catalog::permissive"
  $catalogFolders.Add([ordered]@{
    Name = 'Permissive Mirrors'
    Guid = $permissiveRootGuid
    ParentGuid = $null
    Files = @((Join-Path $Config.PermissiveMirrorsRoot 'README.md'))
  })

  foreach ($repository in $permissiveRepos) {
    $catalogFolders.Add([ordered]@{
      Name = [string]$repository.name
      Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)"
      ParentGuid = $permissiveRootGuid
      Files = @()
    })
  }

  $restrictiveRootGuid = New-StableGuid "$($Config.Name)::catalog::restrictive"
  $catalogFolders.Add([ordered]@{
    Name = 'Restrictive Mirrors'
    Guid = $restrictiveRootGuid
    ParentGuid = $null
    Files = @((Join-Path $Config.RestrictiveMirrorsRoot 'README.md'))
  })

  foreach ($repository in $restrictiveRepos) {
    $catalogFolders.Add([ordered]@{
      Name = [string]$repository.name
      Guid = New-StableGuid "$($Config.Name)::catalog::$($repository.key)"
      ParentGuid = $restrictiveRootGuid
      Files = @()
    })
  }

  if ($cleanRoomDocs.Count -gt 0) {
    $cleanRoomRootGuid = New-StableGuid "$($Config.Name)::catalog::clean-room"
    $catalogFolders.Add([ordered]@{
      Name = 'Clean-Room Specs'
      Guid = $cleanRoomRootGuid
      ParentGuid = $null
      Files = @((Join-Path $Config.CleanRoomSpecsRoot 'README.md')) + $cleanRoomDocs
    })
  }

  Write-SolutionFile -SolutionPath (Join-Path $Config.GeneratedRoot 'catalog.sln') -ProjectEntries $projectEntries -FolderEntries $catalogFolders

  $solutionFilterPath = Join-Path $Config.GeneratedRoot 'active.slnf'
  $relativeProjectPaths = @($projectEntries | ForEach-Object {
    New-RelativePath -BasePath $Config.GeneratedRoot -TargetPath $_.Path
  })

  $solutionFilter = [ordered]@{
    solution = [ordered]@{
      path = 'main.sln'
      projects = $relativeProjectPaths
    }
  } | ConvertTo-Json -Depth 5

  [System.IO.File]::WriteAllText($solutionFilterPath, $solutionFilter + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
}

function Initialize-Workspace {
  param([Parameter(Mandatory = $true)][hashtable]$Config)

  foreach ($path in @(
      $Config.WorkspaceRoot,
      $Config.PlannedAppRoot,
      $Config.MirrorsRoot,
      $Config.PermissiveMirrorsRoot,
      $Config.RestrictiveMirrorsRoot,
      $Config.CleanRoomSpecsRoot,
      $Config.ImplementationWorkspacesRoot,
      $Config.ModelBWorkspacesRoot,
      $Config.ScratchWorkspacesRoot,
      $Config.GeneratedRoot,
      $Config.CacheRoot
    )) {
    Ensure-Directory -Path $path
  }

  Write-WorkspaceReadmes -Config $Config
  Write-WorkspaceGitignore -Config $Config
  Write-ManifestFile -Config $Config -RefreshManifest:$ForceManifestRefresh
  $manifest = Get-Manifest -Config $Config
  Write-WorkspaceArtifacts -Config $Config -Manifest $manifest
}

$productNames = if ($Product -eq 'All') {
  @('ScriptoriumAI', 'HyperTwist', 'VectorShell')
} else {
  @($Product)
}

foreach ($productName in $productNames) {
  $config = Get-ProductConfig -Name $productName -WorkspaceBasePath $WorkspaceBase
  Initialize-Workspace -Config $config
  Write-Host "Initialized external workspace scaffold for $productName at $($config.WorkspaceRoot)" -ForegroundColor Green
}
