<#
.SYNOPSIS
  Addresses residual imbalance in the matched citation analysis (standardised mean difference -0.152
  on citations accumulated through the retraction year) without requiring new data retrieval.
.DESCRIPTION
  Two complementary analyses on the existing 8,448 complete pairs:
   1. Caliper subset. Pairs retained only where the two members differ by at most a set number of
      citations accumulated through the retraction year, removing the imbalance by restriction.
   2. Baseline-stratified paired analysis. Pairs grouped by the machine member's citations accumulated
      through the retraction year; the paired difference is reported within each band, so a difference
      that persists in every band cannot be produced by baseline visibility alone.
.EXAMPLE
  .\Analyze-Rebalance.ps1 -RunDir ..\runs\ai-retractions-v3
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)][string]$RunDir,
    [int[]]$Calipers = @(0, 1, 2, 5),
    [int]$Bootstrap = 2000,
    [int]$Seed = 20260731
)
$ErrorActionPreference = 'Stop'
$tab = Join-Path $RunDir 'tables'

function Get-Q { param([double[]]$S, [double]$P)
    if ($S.Length -eq 0) { return 0.0 }
    $i = ($S.Length - 1) * $P
    $lo = [int][Math]::Floor($i); $hi = [int][Math]::Ceiling($i)
    if ($lo -eq $hi) { return $S[$lo] }
    return $S[$lo] + ($i - $lo) * ($S[$hi] - $S[$lo])
}
function Get-NormalCdf { param([double]$Z)
    if ($Z -lt -8.5) { return 0.0 }; if ($Z -gt 8.5) { return 1.0 }
    $t = 1.0 / (1.0 + 0.2316419 * [Math]::Abs($Z))
    $d = 0.3989422804014327 * [Math]::Exp(-$Z * $Z / 2.0)
    $p = $d * $t * (0.319381530 + $t * (-0.356563782 + $t * (1.781477937 + $t * (-1.821255978 + $t * 1.330274429))))
    if ($Z -ge 0) { return 1.0 - $p }
    return $p
}
function Format-P { param([double]$P)
    if ($P -le 0) { return '< 1e-15' }
    if ($P -lt 0.001) { return ('{0:0.0e+0}' -f $P) }
    return ('{0:0.000}' -f $P)
}
function Get-PZ { param([double]$Z) $a = [Math]::Abs($Z); if ($a -gt 8.2) { return 0.0 }; return 2.0 * (1.0 - (Get-NormalCdf $a)) }
function Get-Smd { param([double[]]$A, [double[]]$B)
    if ($A.Length -lt 2 -or $B.Length -lt 2) { return 0.0 }
    $m1 = ($A | Measure-Object -Average).Average; $m2 = ($B | Measure-Object -Average).Average
    $v1 = 0.0; foreach ($x in $A) { $v1 += ($x - $m1) * ($x - $m1) }; $v1 = $v1 / ($A.Length - 1)
    $v2 = 0.0; foreach ($x in $B) { $v2 += ($x - $m2) * ($x - $m2) }; $v2 = $v2 / ($B.Length - 1)
    $p = [Math]::Sqrt(($v1 + $v2) / 2.0)
    if ($p -le 0) { return 0.0 }
    return ($m1 - $m2) / $p
}
function Test-McNemar { param($Pairs, [scriptblock]$Outcome)
    $b = 0; $c = 0; $both = 0
    foreach ($p in $Pairs) {
        $om = & $Outcome $p.M; $oc = & $Outcome $p.C
        if ($om -and -not $oc) { $b++ } elseif (-not $om -and $oc) { $c++ } elseif ($om -and $oc) { $both++ }
    }
    $n = $Pairs.Count
    if ($n -eq 0) { return $null }
    $chi = 0.0; $p = 1.0
    if (($b + $c) -gt 0) { $chi = [Math]::Pow([Math]::Abs($b - $c) - 1, 2) / ($b + $c); $p = Get-PZ ([Math]::Sqrt($chi)) }
    $diff = ($b - $c) / [double]$n
    $se = [Math]::Sqrt([Math]::Max(0, ($b + $c) - ($b - $c) * ($b - $c) / [double]$n)) / [double]$n
    return [pscustomobject]@{ Pairs = $n
        PctMachine = [Math]::Round(100 * ($b + $both) / [double]$n, 2)
        PctControl = [Math]::Round(100 * ($c + $both) / [double]$n, 2)
        DiffPP = [Math]::Round(100 * $diff, 2)
        CiLo = [Math]::Round(100 * ($diff - 1.959963985 * $se), 2)
        CiHi = [Math]::Round(100 * ($diff + 1.959963985 * $se), 2)
        P = Format-P $p }
}

# rebuild the complete pairs
$raw = @(Import-Csv (Join-Path $tab 't19_citations_full.csv'))
$pairs = @(Import-Csv (Join-Path $tab 't18_matched_pairs.csv'))
$byDoi = @{}
foreach ($r in $raw) { $d = $r.Doi.ToLower(); if (-not $byDoi.ContainsKey($d)) { $byDoi[$d] = $r } }
$complete = New-Object System.Collections.ArrayList
foreach ($p in $pairs) {
    $m = $p.MachineDoi.ToLower(); $c = $p.MatchDoi.ToLower()
    if ($byDoi.ContainsKey($m) -and $byDoi.ContainsKey($c)) { [void]$complete.Add([pscustomobject]@{ M = $byDoi[$m]; C = $byDoi[$c] }) }
}
Write-Host ("Complete pairs: {0}" -f $complete.Count)
$anyPost = { param($r) [int]$r.AfterRetraction -gt 0 }

# ---- 1. caliper subsets ----
$cal = New-Object System.Collections.ArrayList
$full = @($complete | Where-Object { [int]$_.M.ExposureYears -ge 1 -and [int]$_.C.ExposureYears -ge 1 })
$row = Test-McNemar $full $anyPost
$smdFull = Get-Smd ([double[]]@($full | ForEach-Object { [double]$_.M.UpToRetraction })) ([double[]]@($full | ForEach-Object { [double]$_.C.UpToRetraction }))
[void]$cal.Add([pscustomobject]@{ Caliper = 'none (all pairs with exposure)'; Pairs = $row.Pairs; BaselineSmd = [Math]::Round($smdFull, 3)
        PctMachine = $row.PctMachine; PctControl = $row.PctControl; DiffPP = $row.DiffPP; CiLo = $row.CiLo; CiHi = $row.CiHi; P = $row.P })
foreach ($k in $Calipers) {
    $sub = @($full | Where-Object { [Math]::Abs([int]$_.M.UpToRetraction - [int]$_.C.UpToRetraction) -le $k })
    if ($sub.Count -lt 30) { continue }
    $r2 = Test-McNemar $sub $anyPost
    $smd = Get-Smd ([double[]]@($sub | ForEach-Object { [double]$_.M.UpToRetraction })) ([double[]]@($sub | ForEach-Object { [double]$_.C.UpToRetraction }))
    [void]$cal.Add([pscustomobject]@{ Caliper = ("within " + $k + " citation(s)"); Pairs = $r2.Pairs; BaselineSmd = [Math]::Round($smd, 3)
            PctMachine = $r2.PctMachine; PctControl = $r2.PctControl; DiffPP = $r2.DiffPP; CiLo = $r2.CiLo; CiHi = $r2.CiHi; P = $r2.P })
}
$cal | Export-Csv (Join-Path $tab 't37_caliper_subsets.csv') -NoTypeInformation -Encoding UTF8

# ---- 2. baseline-stratified ----
$bands = @(
    @{ L = '0'; Lo = 0; Hi = 0 }, @{ L = '1 to 2'; Lo = 1; Hi = 2 }, @{ L = '3 to 5'; Lo = 3; Hi = 5 },
    @{ L = '6 to 10'; Lo = 6; Hi = 10 }, @{ L = '11 or more'; Lo = 11; Hi = 1000000 }
)
$strat = New-Object System.Collections.ArrayList
foreach ($b in $bands) {
    $sub = @($full | Where-Object { [int]$_.M.UpToRetraction -ge $b.Lo -and [int]$_.M.UpToRetraction -le $b.Hi })
    if ($sub.Count -lt 30) { continue }
    $r3 = Test-McNemar $sub $anyPost
    [void]$strat.Add([pscustomobject]@{ BaselineBand = $b.L; Pairs = $r3.Pairs; PctMachine = $r3.PctMachine; PctControl = $r3.PctControl
            DiffPP = $r3.DiffPP; CiLo = $r3.CiLo; CiHi = $r3.CiHi; P = $r3.P })
}
$strat | Export-Csv (Join-Path $tab 't38_baseline_stratified.csv') -NoTypeInformation -Encoding UTF8

$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine("# Residual-imbalance analyses for the paired citation comparison")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("Generated $(Get-Date -Format 'yyyy-MM-dd HH:mm'). Outcome: cited in any post-retraction calendar year.")
[void]$sb.AppendLine("Both analyses use only the existing complete pairs, so no further data retrieval was required.")
[void]$sb.AppendLine("")
[void]$sb.AppendLine("## Caliper restriction on citations accumulated through the retraction year")
[void]$sb.AppendLine("| Caliper | Pairs | Baseline SMD | Machine % | Control % | Paired difference pp (95% CI) | p |")
[void]$sb.AppendLine("|---|---|---|---|---|---|---|")
foreach ($c in $cal) { [void]$sb.AppendLine("| $($c.Caliper) | $($c.Pairs) | $($c.BaselineSmd) | $($c.PctMachine) | $($c.PctControl) | $($c.DiffPP) ($($c.CiLo) to $($c.CiHi)) | $($c.P) |") }
[void]$sb.AppendLine("")
[void]$sb.AppendLine("## Paired difference within bands of baseline citation count")
[void]$sb.AppendLine("| Baseline band (machine member) | Pairs | Machine % | Control % | Paired difference pp (95% CI) | p |")
[void]$sb.AppendLine("|---|---|---|---|---|---|")
foreach ($s in $strat) { [void]$sb.AppendLine("| $($s.BaselineBand) | $($s.Pairs) | $($s.PctMachine) | $($s.PctControl) | $($s.DiffPP) ($($s.CiLo) to $($s.CiHi)) | $($s.P) |") }
Set-Content -Path (Join-Path $RunDir 'rebalance-v6.md') -Value $sb.ToString() -Encoding UTF8
Write-Host "Wrote rebalance-v6.md and tables t37-t38" -ForegroundColor Green
$cal | Format-Table -AutoSize
$strat | Format-Table -AutoSize
