param(
  [int]$SmokePort = 4318,
  [string]$SmokeToken = 'smoke-token',
  [string]$BaseOutputDir,
  [string]$AppExePath
)

$ErrorActionPreference = 'Stop'

function Write-Utf8File {
  param([string]$FilePath, [string]$Content)
  $encoding = New-Object System.Text.UTF8Encoding $false
  [System.IO.File]::WriteAllText($FilePath, $Content, $encoding)
}

function Get-XhsVolcesImageProviderConfig {
  $modelId = 'doubao-seedream-5-0-260128'
  return [ordered]@{
    providerName = 'volces'
    modelId = $modelId
    providers = [ordered]@{
      volces = [ordered]@{
        baseUrl = 'https://ark.cn-beijing.volces.com/api/v3/images/generations'
        apiKey = 'c434d7a4-ac38-48c4-9d50-ce0e3c9aaf6b'
        api = 'openai-completions'
        models = @(
          [ordered]@{
            id = $modelId
            name = 'volces-gen-image'
            reasoning = $false
            input = @('text', 'image')
            cost = [ordered]@{
              input = 0
              output = 0
              cacheRead = 0
              cacheWrite = 0
            }
            contextWindow = 128000
            maxTokens = 81920
          }
        )
      }
    }
  }
}

function Get-XhsQwenTextProviderConfig {
  $modelId = 'qwen3.5-plus'
  return [ordered]@{
    providerName = 'qwen'
    modelId = $modelId
    providers = [ordered]@{
      qwen = [ordered]@{
        baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
        apiKey = 'sk-cfd27311f00649438753fac04851585b'
        api = 'openai-completions'
        models = @(
          [ordered]@{
            id = $modelId
            name = 'qwen-chat'
            reasoning = $false
            input = @('text')
            cost = [ordered]@{
              input = 0
              output = 0
              cacheRead = 0
              cacheWrite = 0
            }
            contextWindow = 128000
            maxTokens = 8192
          }
        )
      }
    }
  }
}

function Copy-ProjectBundleSource {
  param(
    [string]$SourceRoot,
    [string]$DestinationRoot
  )

  $projectJsonPath = Join-Path $SourceRoot 'project.json'
  $excludePaths = @()
  if (Test-Path $projectJsonPath) {
    $projectConfig = Get-Content -Path $projectJsonPath -Raw | ConvertFrom-Json
    $configuredExcludes = $projectConfig.bundlePackaging.excludePaths
    if ($configuredExcludes) {
      $excludePaths = @($configuredExcludes | ForEach-Object { [string]$_ })
    }
  }
  $excludeSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
  foreach ($entry in $excludePaths) {
    if ($entry) {
      [void]$excludeSet.Add($entry)
    }
  }

  New-Item -ItemType Directory -Force -Path $DestinationRoot | Out-Null
  foreach ($entry in Get-ChildItem -LiteralPath $SourceRoot -Force) {
    if ($excludeSet.Contains($entry.Name)) {
      continue
    }
    if ($entry.PSIsContainer -and $entry.Name -match '^tmp[a-z0-9]{6,}$') {
      Write-Warning "Skipping transient temp directory from bundle source: $($entry.FullName)"
      continue
    }
    try {
      Copy-Item -LiteralPath $entry.FullName -Destination (Join-Path $DestinationRoot $entry.Name) -Recurse -Force
    }
    catch [System.UnauthorizedAccessException] {
      Write-Warning "Skipping inaccessible bundle source entry: $($entry.FullName)"
      continue
    }
  }
}

function Install-XhsNodeDependencies {
  param([string]$ProjectRoot)

  $packageJsonPath = Join-Path $ProjectRoot 'package.json'
  $packageLockPath = Join-Path $ProjectRoot 'package-lock.json'
  if (-not (Test-Path $packageJsonPath) -or -not (Test-Path $packageLockPath)) {
    throw "XHS bundle staging directory is missing package.json or package-lock.json: $ProjectRoot"
  }

  Write-Host "Installing XHS Node dependencies in staging directory: $ProjectRoot"
  Push-Location $ProjectRoot
  try {
    npm ci --omit=dev --no-audit --fund=false
    if ($LASTEXITCODE -ne 0) {
      throw "npm ci failed while preparing XHS bundle staging directory."
    }
  } finally {
    Pop-Location
  }
}

function Assert-XhsBundleNodeDependencies {
  param([string]$ZipPath)

  Add-Type -AssemblyName System.IO.Compression.FileSystem
  $archive = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
  try {
    $entries = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($entry in $archive.Entries) {
      [void]$entries.Add($entry.FullName.Replace('\', '/'))
    }
    foreach ($requiredEntry in @(
      'xhs/node_modules/crypto-js/package.json',
      'xhs/node_modules/jsdom/package.json'
    )) {
      if (-not $entries.Contains($requiredEntry)) {
        throw "XHS bundle zip is missing required Node dependency entry: $requiredEntry"
      }
    }
  } finally {
    $archive.Dispose()
  }
}

function New-ExpertFixtureProject {
  param(
    [string]$ProjectsRoot,
    [string]$ProjectId,
    [string]$ProjectName,
    [string]$Platform,
    [string]$Description,
    [string]$UpdatedAt
  )

  $projectRoot = Join-Path $ProjectsRoot $ProjectId
  New-Item -ItemType Directory -Force -Path $projectRoot, (Join-Path $projectRoot 'memory') | Out-Null

  $projectPayload = [ordered]@{
    id = $ProjectId
    name = $ProjectName
    description = $Description
    platform = $Platform
    ready = $true
    updatedAt = $UpdatedAt
    boundSkillIds = @()
    workspaceEntryEnabled = $true
  }

  Write-Utf8File (Join-Path $projectRoot 'project.json') ($projectPayload | ConvertTo-Json -Depth 8)
  Write-Utf8File (Join-Path $projectRoot 'README.md') "# $ProjectName`n`nFixture expert project for manual XHS validation."
  Write-Utf8File (Join-Path $projectRoot 'AGENTS.md') "# $ProjectName`n`nPassive fixture expert used to keep the experts list above two items."
}

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if (-not $BaseOutputDir) {
  $BaseOutputDir = Join-Path $repoRoot '.tmp\xhs-expert-manual-launch'
}
if (-not $AppExePath) {
  $AppExePath = Join-Path $repoRoot 'dist\installer\win-unpacked\千匠问天.exe'
}

$BaseOutputDir = [System.IO.Path]::GetFullPath($BaseOutputDir)
$AppExePath = [System.IO.Path]::GetFullPath($AppExePath)
$xhsSourceRoot = Join-Path $repoRoot 'workspace\xhs'
$userDataPath = Join-Path $BaseOutputDir 'user-data'
$logsPath = Join-Path $BaseOutputDir 'logs'
$bundleSourceRoot = Join-Path $BaseOutputDir 'bundle-src'
$bundleZipPath = Join-Path $BaseOutputDir 'xhs-expert-manual-launch.zip'
$bundleFileName = 'xhs-expert-manual-launch.zip'
$bundleSkillId = 'xhs-project-bundle'
$bundleConfigVersion = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')

if (-not (Test-Path $AppExePath)) {
  throw "Packaged desktop executable was not found: $AppExePath"
}
if (-not (Test-Path $xhsSourceRoot)) {
  throw "XHS workspace source was not found: $xhsSourceRoot"
}

$runningDesktop = Get-Process -Name '千匠问天' -ErrorAction SilentlyContinue
if ($runningDesktop) {
  $processIds = ($runningDesktop | Select-Object -ExpandProperty Id) -join ', '
  throw "千匠问天 is already running (PID: $processIds). Close the desktop app first, then rerun this launcher."
}

if (Test-Path $BaseOutputDir) {
  Remove-Item -LiteralPath $BaseOutputDir -Recurse -Force -ErrorAction SilentlyContinue
}
if (Test-Path $BaseOutputDir) {
  throw "Failed to reset manual launch directory: $BaseOutputDir"
}

New-Item -ItemType Directory -Force -Path $BaseOutputDir, $userDataPath, $logsPath, $bundleSourceRoot | Out-Null

$stagedXhsRoot = Join-Path $bundleSourceRoot 'xhs'
Copy-ProjectBundleSource -SourceRoot $xhsSourceRoot -DestinationRoot $stagedXhsRoot
Install-XhsNodeDependencies -ProjectRoot $stagedXhsRoot
if (Test-Path $bundleZipPath) {
  Remove-Item -LiteralPath $bundleZipPath -Force
}
Compress-Archive -Path (Join-Path $bundleSourceRoot 'xhs') -DestinationPath $bundleZipPath -Force
Assert-XhsBundleNodeDependencies -ZipPath $bundleZipPath

$projectsRoot = Join-Path $userDataPath 'projects'
$manifestsRoot = Join-Path $userDataPath 'manifests'
New-Item -ItemType Directory -Force -Path $projectsRoot, $manifestsRoot | Out-Null

New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'douyin-expert-smoke' -ProjectName 'Douyin Expert Fixture' -Platform 'douyin' -Description 'Fixture project that keeps the experts list above two items.' -UpdatedAt '2026-04-03T00:00:00.000Z'
New-ExpertFixtureProject -ProjectsRoot $projectsRoot -ProjectId 'browser-expert-smoke' -ProjectName 'Browser Expert Fixture' -Platform 'browser' -Description 'Fixture project that keeps the experts list above two items.' -UpdatedAt '2026-04-03T00:01:00.000Z'
Write-Utf8File (Join-Path $manifestsRoot 'active-project.json') (@{ projectId = 'browser-expert-smoke' } | ConvertTo-Json -Depth 3)

$env:QJCLAW_USER_DATA_PATH = $userDataPath
$env:QJCLAW_LOGS_PATH = $logsPath
$env:QJCLAW_RUNTIME_MODE = 'bundled-runtime'
$env:QJCLAW_SMOKE_CLOUD_API_BASE_URL = "http://127.0.0.1:$SmokePort"
$env:QJCLAW_SMOKE_AUTH_TOKEN = $SmokeToken
$env:QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY = 'smoke-runtime-api-key'
$env:QJCLAW_SMOKE_BUNDLE_ZIP_PATH = $bundleZipPath
$env:QJCLAW_SMOKE_BUNDLE_FILE_NAME = $bundleFileName
$env:QJCLAW_SMOKE_BUNDLE_SKILL_ID = $bundleSkillId
$env:QJCLAW_SMOKE_BUNDLE_SKILL_TITLE = 'XHS Project Bundle'
$env:QJCLAW_SMOKE_BUNDLE_SKILL_DESCRIPTION = 'Local mock employee-config bundle for manual Xiaohongshu expert validation.'
$env:QJCLAW_SMOKE_BUNDLE_CONFIG_VERSION = $bundleConfigVersion
$xhsImageProviderConfig = Get-XhsVolcesImageProviderConfig
$xhsTextProviderConfig = Get-XhsQwenTextProviderConfig
$combinedProviders = [ordered]@{}
foreach ($entry in $xhsTextProviderConfig.providers.GetEnumerator()) {
  $combinedProviders[$entry.Key] = $entry.Value
}
foreach ($entry in $xhsImageProviderConfig.providers.GetEnumerator()) {
  $combinedProviders[$entry.Key] = $entry.Value
}
$env:QJCLAW_EXTRA_MODEL_PROVIDERS_JSON = ($combinedProviders | ConvertTo-Json -Depth 10 -Compress)
$env:XHS_LLM_PROVIDER = $xhsTextProviderConfig.providerName
$env:QWEN_BASE_URL = [string]$xhsTextProviderConfig.providers.qwen.baseUrl
$env:QWEN_API_KEY = [string]$xhsTextProviderConfig.providers.qwen.apiKey
$env:QWEN_MODEL = $xhsTextProviderConfig.modelId
$env:XHS_IMAGE_PROVIDER = $xhsImageProviderConfig.providerName
$env:XHS_IMAGE_BASE_URL = [string]$xhsImageProviderConfig.providers.volces.baseUrl
$env:XHS_IMAGE_API_KEY = [string]$xhsImageProviderConfig.providers.volces.apiKey
$env:XHS_IMAGE_MODEL = $xhsImageProviderConfig.modelId

$null = Start-Process -FilePath $AppExePath -PassThru

Write-Host "Launched packaged desktop app with local mock employee-config."
Write-Host "User data path: $userDataPath"
Write-Host "Bundle zip path: $bundleZipPath"
Write-Host "Configured XHS text provider: $($xhsTextProviderConfig.providerName)/$($xhsTextProviderConfig.modelId)"
Write-Host "Configured XHS image provider: $($xhsImageProviderConfig.providerName)/$($xhsImageProviderConfig.modelId)"
Write-Host "Open the Experts view, select xhs, and send your manual prompt."
