<#
.SYNOPSIS
  Renders Fig3: forest plot of stratum-specific dispersion ratios (machine / comparison)
  for time to retraction, within exact strata of retraction year x publication year x publisher.
.DESCRIPTION
  Answers the referee request for "a forest plot of publisher-year-specific dispersion
  effects". Grayscale, pattern- and shape-coded so it survives both colourblindness and
  black-and-white printing. 600 dpi TIFF sized for a Springer single-column text area.
  Reads t45_stratum_dispersion.csv and t46_matched_dispersion_summary.csv.
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)][string]$RunDir,
    [Parameter(Mandatory = $true)][string]$OutDir,
    [int]$Dpi = 600,
    [double]$WidthMm = 174,
    [double]$HeightMm = 200
)
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null

# Restrict to the PRIMARY configuration. t45 now contains three configurations and plotting
# them together would mix the exact-publisher result with its own sensitivity analyses.
$rows = @(Import-Csv (Join-Path $RunDir 'tables\t45_stratum_dispersion.csv') |
    Where-Object { $_.MadRatio -ne '' -and $_.Configuration -like 'PRIMARY*' } |
    Sort-Object { [double]$_.MadRatio })
if ($rows.Count -eq 0) { throw "no informative strata found in t45_stratum_dispersion.csv" }

$prim = @(Import-Csv (Join-Path $RunDir 'tables\t46_matched_dispersion_summary.csv') |
    Where-Object { $_.Configuration -like 'PRIMARY*' })[0]
if (-not $prim) { throw "no PRIMARY configuration row in t46_matched_dispersion_summary.csv" }
$matchedRatio  = [double]$prim.MatchedMadRatio
$unadjustedRat = [double]$prim.UnadjustedMadRatio

$scale = $Dpi / 96.0
$W = [int]($WidthMm / 25.4 * $Dpi)
$H = [int]($HeightMm / 25.4 * $Dpi)

$bmp = New-Object System.Drawing.Bitmap $W, $H
$bmp.SetResolution($Dpi, $Dpi)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAliasGridFit
$g.Clear([System.Drawing.Color]::White)

$fontRow  = New-Object System.Drawing.Font('Arial', (7.5 * $scale), [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
$fontAxis = New-Object System.Drawing.Font('Arial', (9 * $scale), [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
$fontLab  = New-Object System.Drawing.Font('Arial', (10 * $scale), [System.Drawing.FontStyle]::Regular, [System.Drawing.GraphicsUnit]::Pixel)
$fontBold = New-Object System.Drawing.Font('Arial', (9 * $scale), [System.Drawing.FontStyle]::Bold, [System.Drawing.GraphicsUnit]::Pixel)

$black  = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::Black)
$grey   = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(120, 120, 120))
$penAx  = New-Object System.Drawing.Pen ([System.Drawing.Color]::Black), (1.0 * $scale)
$penRef = New-Object System.Drawing.Pen ([System.Drawing.Color]::Black), (1.0 * $scale)
$penRef.DashStyle = [System.Drawing.Drawing2D.DashStyle]::Dash
$penGrid = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(205, 205, 205)), (0.6 * $scale)

# layout
$L = [int](116 * $scale)      # left margin for stratum labels
$R = [int](10 * $scale)
$T = [int](46 * $scale)
$B = [int](34 * $scale)
$plotW = $W - $L - $R
$plotH = $H - $T - $B

# x scale: MAD ratio, linear, 0 to max(2.2, observed max)
$maxRatio = [Math]::Max(2.2, (($rows | ForEach-Object { [double]$_.MadRatio } | Measure-Object -Maximum).Maximum))
$xOf = { param($v) $L + ([Math]::Min([double]$v, $maxRatio) / $maxRatio) * $plotW }

# gridlines and x ticks
foreach ($t in 0.0, 0.5, 1.0, 1.5, 2.0) {
    if ($t -gt $maxRatio) { continue }
    $x = & $xOf $t
    $g.DrawLine($penGrid, $x, $T, $x, ($T + $plotH))
    $s = $t.ToString('0.0')
    $sz = $g.MeasureString($s, $fontAxis)
    $g.DrawString($s, $fontAxis, $black, ($x - $sz.Width / 2), ($T + $plotH + 4 * $scale))
}

# reference line at ratio = 1 (equal dispersion)
$x1 = & $xOf 1.0
$g.DrawLine($penRef, $x1, $T, $x1, ($T + $plotH))

# rows
$n = $rows.Count
$rowH = $plotH / ($n + 3.0)     # leave space for two summary rows
$maxN = (($rows | ForEach-Object { [int]$_.NMachine } | Measure-Object -Maximum).Maximum)

for ($i = 0; $i -lt $n; $i++) {
    $r = $rows[$i]
    $y = $T + ($i + 0.5) * $rowH
    $ratio = [double]$r.MadRatio
    $x = & $xOf $ratio

    # marker area proportional to machine n (min 2.2 px, max 7 px radius)
    $rad = (2.2 + 4.8 * [Math]::Sqrt([double]$r.NMachine / $maxN)) * $scale
    $g.FillEllipse($black, ($x - $rad), ($y - $rad), (2 * $rad), (2 * $rad))

    # t45 stores the normalised (lower-case) publisher key; restore readable casing and
    # shorten the few long imprint names so labels fit the gutter.
    $pubShort = switch -Wildcard ($r.Publisher) {
        'ios press*'          { 'IOS Press' }
        'springer - nature*'  { 'Springer NPG' }
        'elsevier - cell*'    { 'Elsevier (Cell Press)' }
        'iop publishing*'     { 'IOP Publishing' }
        'plos*'               { 'PLoS' }
        default {
            (Get-Culture).TextInfo.ToTitleCase($r.Publisher)
        }
    }
    $lab = "$($r.RetractionYear)/$($r.PublicationYear) $pubShort"
    $sz = $g.MeasureString($lab, $fontRow)
    $g.DrawString($lab, $fontRow, $black, ($L - 4 * $scale - $sz.Width), ($y - $sz.Height / 2))
}

# summary rows: matched estimate and unadjusted estimate, drawn as diamonds
function Draw-Diamond {
    param($cx, $cy, $rr, $brush)
    $pts = @(
        (New-Object System.Drawing.PointF (($cx), ($cy - $rr))),
        (New-Object System.Drawing.PointF (($cx + $rr), ($cy))),
        (New-Object System.Drawing.PointF (($cx), ($cy + $rr))),
        (New-Object System.Drawing.PointF (($cx - $rr), ($cy)))
    )
    $g.FillPolygon($brush, $pts)
}

$ySum1 = $T + ($n + 1.1) * $rowH
$xm = & $xOf $matchedRatio
Draw-Diamond -cx $xm -cy $ySum1 -rr (7 * $scale) -brush $black
$lab1 = 'Matched 1:1 pooled'
$sz1 = $g.MeasureString($lab1, $fontBold)
$g.DrawString($lab1, $fontBold, $black, ($L - 4 * $scale - $sz1.Width), ($ySum1 - $sz1.Height / 2))

$ySum2 = $T + ($n + 2.2) * $rowH
$xu = & $xOf $unadjustedRat
Draw-Diamond -cx $xu -cy $ySum2 -rr (7 * $scale) -brush $grey
$lab2 = 'Unadjusted cohort'
$sz2 = $g.MeasureString($lab2, $fontBold)
$g.DrawString($lab2, $fontBold, $grey, ($L - 4 * $scale - $sz2.Width), ($ySum2 - $sz2.Height / 2))

# axes
$g.DrawLine($penAx, $L, ($T + $plotH), ($L + $plotW), ($T + $plotH))

# axis title
$xt = 'Dispersion ratio: machine-attributed MAD / comparison MAD'
$szx = $g.MeasureString($xt, $fontLab)
$g.DrawString($xt, $fontLab, $black, ($L + ($plotW - $szx.Width) / 2), ($H - $B + 16 * $scale))

# save as LZW TIFF
$codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() |
    Where-Object { $_.MimeType -eq 'image/tiff' }
$ep = New-Object System.Drawing.Imaging.EncoderParameters 1
$ep.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter (
    [System.Drawing.Imaging.Encoder]::Compression,
    [int][System.Drawing.Imaging.EncoderValue]::CompressionLZW)
$out = Join-Path $OutDir 'Fig3.tif'
$bmp.Save($out, $codec, $ep)

# png preview
$pw = 1030; $ph = [int]($H * $pw / $W)
$pv = New-Object System.Drawing.Bitmap $pw, $ph
$pg = [System.Drawing.Graphics]::FromImage($pv)
$pg.InterpolationMode = 'HighQualityBicubic'
$pg.DrawImage($bmp, 0, 0, $pw, $ph)
$pv.Save((Join-Path $OutDir 'Fig3-preview.png'), [System.Drawing.Imaging.ImageFormat]::Png)
$pg.Dispose(); $pv.Dispose()

$g.Dispose(); $bmp.Dispose()
Write-Host ("Wrote Fig3.tif ({0} x {1} px at {2} dpi, {3} strata)" -f $W, $H, $Dpi, $n) -ForegroundColor Green
Write-Host ("  matched ratio {0}, unadjusted ratio {1}" -f $matchedRatio, $unadjustedRat) -ForegroundColor Yellow
