param(
  [string]$ProjectRoot = "",
  [switch]$AsJson,
  [switch]$AllowWarningsOnly
)

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

if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
  $ProjectRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot "..\.."))
}

function New-Issue {
  param(
    [string]$Severity,
    [string]$File,
    [string]$Section,
    [string]$Message
  )

  return [PSCustomObject]@{
    severity = $Severity
    file = $File
    section = $Section
    message = $Message
  }
}

function Get-FileContentOrNull {
  param([string]$Path)

  if (-not (Test-Path -LiteralPath $Path)) {
    return $null
  }

  return Get-Content -LiteralPath $Path -Raw
}

function Test-ContainsPlaceholder {
  param([string]$Content)

  if ([string]::IsNullOrWhiteSpace($Content)) {
    return $false
  }

  return (
    $Content -match '(?im)\bTBD\b' -or
    $Content -match '(?im)\bTODO\b' -or
    $Content -match '(?im)<replace-' -or
    $Content -match '(?im)REQ-REPLACE|FR-REPLACE|NFR-REPLACE|META-HANDOFF-REPLACE'
  )
}

function Test-HasHeading {
  param(
    [string]$Content,
    [string]$Heading
  )

  return $Content -match ("(?im)^\s*##\s+" + [regex]::Escape($Heading) + "\s*$")
}

function Test-HasNonEmptyTableRow {
  param(
    [string]$Content,
    [string]$AnchorHeading,
    [string[]]$DisallowedTokens = @("TBD", "TODO")
  )

  $pattern = '(?ims)^\s*##\s+' + [regex]::Escape($AnchorHeading) + '\s*$([\s\S]*?)(?=^\s*##\s+|\z)'
  $match = [regex]::Match($Content, $pattern)
  if (-not $match.Success) {
    return $false
  }

  $lines = $match.Groups[1].Value -split "`r?`n"
  foreach ($line in $lines) {
    if ($line.Trim().StartsWith("|")) {
      $trimmed = $line.Trim()
      if ($trimmed -match '^\|\s*-+\s*\|') {
        continue
      }

      $bad = $false
      foreach ($token in $DisallowedTokens) {
        if ($trimmed -match [regex]::Escape($token)) {
          $bad = $true
          break
        }
      }

      if (-not $bad -and $trimmed -notmatch '^\|\s*(来源 ID|用户 ID|场景 ID|规则 ID|REQ ID|AC ID|标准 ID|风险 ID|对象)\s*\|') {
        return $true
      }
    }
  }

  return $false
}

function Test-HasSectionWithResolvedBullets {
  param(
    [string]$Content,
    [string]$Heading,
    [string[]]$RequiredTerms = @(),
    [string[]]$DisallowedTokens = @("TBD", "TODO")
  )

  $pattern = '(?ims)^\s*##\s+' + [regex]::Escape($Heading) + '\s*$([\s\S]*?)(?=^\s*##\s+|\z)'
  $match = [regex]::Match($Content, $pattern)
  if (-not $match.Success) {
    return $false
  }

  $section = $match.Groups[1].Value
  foreach ($term in $RequiredTerms) {
    if ($section -notmatch [regex]::Escape($term)) {
      return $false
    }
  }

  $bulletLines = @($section -split "`r?`n" | Where-Object { $_.Trim() -match '^[-*]\s+' })
  if ($bulletLines.Count -eq 0) {
    return $false
  }

  foreach ($line in $bulletLines) {
    $hasBadToken = $false
    foreach ($token in $DisallowedTokens) {
      if ($line -match [regex]::Escape($token)) {
        $hasBadToken = $true
        break
      }
    }

    if (-not $hasBadToken) {
      return $true
    }
  }

  return $false
}

function Test-HasGivenWhenThenRows {
  param([string]$Content)

  $match = [regex]::Match(
    $Content,
    '(?ims)^\s*##\s+验收标准\s*$([\s\S]*?)(?=^\s*##\s+|\z)'
  )
  if (-not $match.Success) {
    return $false
  }

  $lines = $match.Groups[1].Value -split "`r?`n"
  foreach ($line in $lines) {
    $trimmed = $line.Trim()
    if ($trimmed -match '^\|\s*AC-\d+' -and $trimmed -notmatch '\bTBD\b') {
      return $true
    }
  }

  return $false
}

$issues = @()

$proposalPath = Join-Path $ProjectRoot "docs\spec\03-proposal-and-scope.md"
$prdPath = Join-Path $ProjectRoot "docs\spec\04-prd.md"
$storiesPath = Join-Path $ProjectRoot "docs\spec\05-user-stories-and-acceptance.md"
$rtmPath = Join-Path $ProjectRoot "docs\spec\14-rtm.md"

$proposalContent = Get-FileContentOrNull -Path $proposalPath
$prdContent = Get-FileContentOrNull -Path $prdPath
$storiesContent = Get-FileContentOrNull -Path $storiesPath
$rtmContent = Get-FileContentOrNull -Path $rtmPath

if ($null -eq $proposalContent) {
  $issues += New-Issue -Severity "error" -File "docs/spec/03-proposal-and-scope.md" -Section "file" -Message "缺少 03 立项提案与范围说明。"
}
else {
  if (Test-ContainsPlaceholder -Content $proposalContent) {
    $issues += New-Issue -Severity "error" -File "docs/spec/03-proposal-and-scope.md" -Section "placeholders" -Message "03 仍包含 TBD/TODO/占位符，需求范围未收敛完成。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $proposalContent -AnchorHeading "Phase 边界")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/03-proposal-and-scope.md" -Section "Phase 边界" -Message "03 缺少已落实的 In Scope / Out of Scope。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $proposalContent -AnchorHeading "成功标准")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/03-proposal-and-scope.md" -Section "成功标准" -Message "03 缺少可验证成功标准。"
  }
  if (-not (Test-HasSectionWithResolvedBullets -Content $proposalContent -Heading "需求收敛检查清单" -RequiredTerms @("变更类型", "不可修改范围", "兼容要求", "影响面", "待确认问题"))) {
    $issues += New-Issue -Severity "error" -File "docs/spec/03-proposal-and-scope.md" -Section "需求收敛检查清单" -Message "03 缺少第一章要求的需求收敛清单：变更类型、no-touch、兼容要求、影响面、待确认问题。"
  }
}

if ($null -eq $prdContent) {
  $issues += New-Issue -Severity "error" -File "docs/spec/04-prd.md" -Section "file" -Message "缺少 04 产品需求说明 PRD。"
}
else {
  if (Test-ContainsPlaceholder -Content $prdContent) {
    $issues += New-Issue -Severity "error" -File "docs/spec/04-prd.md" -Section "placeholders" -Message "04 仍包含 TBD/TODO/占位符，产品语义未收敛完成。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $prdContent -AnchorHeading "业务场景")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/04-prd.md" -Section "业务场景" -Message "04 缺少已定义业务场景。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $prdContent -AnchorHeading "产品规则")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/04-prd.md" -Section "产品规则" -Message "04 缺少已定义产品规则。"
  }
  if (-not (Test-HasSectionWithResolvedBullets -Content $prdContent -Heading "输入输出与异常" -RequiredTerms @("输入", "输出", "异常场景"))) {
    $issues += New-Issue -Severity "error" -File "docs/spec/04-prd.md" -Section "输入输出与异常" -Message "04 缺少输入、输出、异常场景定义。"
  }
}

if ($null -eq $storiesContent) {
  $issues += New-Issue -Severity "error" -File "docs/spec/05-user-stories-and-acceptance.md" -Section "file" -Message "缺少 05 用户故事与验收标准。"
}
else {
  if (Test-ContainsPlaceholder -Content $storiesContent) {
    $issues += New-Issue -Severity "error" -File "docs/spec/05-user-stories-and-acceptance.md" -Section "placeholders" -Message "05 仍包含 TBD/TODO/占位符，用户故事或验收标准未收敛完成。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $storiesContent -AnchorHeading "用户故事")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/05-user-stories-and-acceptance.md" -Section "用户故事" -Message "05 缺少可追溯的 REQ 用户故事。"
  }
  if (-not (Test-HasGivenWhenThenRows -Content $storiesContent)) {
    $issues += New-Issue -Severity "error" -File "docs/spec/05-user-stories-and-acceptance.md" -Section "验收标准" -Message "05 缺少已落实的 Given/When/Then 验收标准。"
  }
  if (-not (Test-HasSectionWithResolvedBullets -Content $storiesContent -Heading "边界与兼容要求" -RequiredTerms @("不可修改范围", "兼容旧逻辑", "非目标范围"))) {
    $issues += New-Issue -Severity "error" -File "docs/spec/05-user-stories-and-acceptance.md" -Section "边界与兼容要求" -Message "05 缺少用户故事级边界与兼容要求。"
  }
}

if ($null -eq $rtmContent) {
  $issues += New-Issue -Severity "error" -File "docs/spec/14-rtm.md" -Section "file" -Message "缺少 14 需求追踪矩阵 RTM。"
}
else {
  if (Test-ContainsPlaceholder -Content $rtmContent) {
    $issues += New-Issue -Severity "error" -File "docs/spec/14-rtm.md" -Section "placeholders" -Message "14 仍包含 TBD/TODO/占位符，追溯链路未收敛完成。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $rtmContent -AnchorHeading "RTM")) {
    $issues += New-Issue -Severity "error" -File "docs/spec/14-rtm.md" -Section "RTM" -Message "14 缺少已落实的 REQ -> Test -> Evidence 追溯。"
  }
  if (-not (Test-HasNonEmptyTableRow -Content $rtmContent -AnchorHeading "反向检查")) {
    $issues += New-Issue -Severity "warning" -File "docs/spec/14-rtm.md" -Section "反向检查" -Message "14 缺少反向检查记录，影响影响面与接口 owner 对账。"
  }
  if (-not (Test-HasSectionWithResolvedBullets -Content $rtmContent -Heading "影响面与回归范围" -RequiredTerms @("直接影响", "间接影响", "回归范围"))) {
    $issues += New-Issue -Severity "error" -File "docs/spec/14-rtm.md" -Section "影响面与回归范围" -Message "14 缺少影响面与回归范围定义，无法覆盖第一章的影响范围痛点。"
  }
}

$errors = @($issues | Where-Object { $_.severity -eq "error" })
$warnings = @($issues | Where-Object { $_.severity -eq "warning" })
$exitCode = if ($errors.Count -gt 0) { 1 } elseif ($warnings.Count -gt 0 -and -not $AllowWarningsOnly) { 2 } else { 0 }

if ($AsJson) {
  [PSCustomObject]@{
    ok = ($exitCode -eq 0)
    errors = $errors
    warnings = $warnings
  } | ConvertTo-Json -Depth 6
  exit $exitCode
}

if ($errors.Count -eq 0 -and $warnings.Count -eq 0) {
  Write-Output "Spec lint passed."
  exit 0
}

if ($errors.Count -gt 0) {
  Write-Output "Spec lint failed."
  foreach ($issue in $errors) {
    Write-Output ("- [ERROR] {0} :: {1} :: {2}" -f $issue.file, $issue.section, $issue.message)
  }
}

if ($warnings.Count -gt 0) {
  if ($errors.Count -eq 0) {
    Write-Output "Spec lint passed with warnings."
  }
  foreach ($issue in $warnings) {
    Write-Output ("- [WARNING] {0} :: {1} :: {2}" -f $issue.file, $issue.section, $issue.message)
  }
}

exit $exitCode
