$ErrorActionPreference = "Stop"

Add-Type -AssemblyName System.Drawing

$repo = $PSScriptRoot
$outputDir = Join-Path $repo "samples\roi_preview"

if (Test-Path $outputDir) {
    Remove-Item $outputDir -Recurse -Force
}
New-Item -ItemType Directory -Path $outputDir | Out-Null

$imageExts = @(".jpg", ".jpeg", ".png", ".bmp", ".webp")

function New-Box {
    param(
        [double]$X1,
        [double]$Y1,
        [double]$X2,
        [double]$Y2
    )
    [PSCustomObject]@{
        X1 = $X1
        Y1 = $Y1
        X2 = $X2
        Y2 = $Y2
        W = [Math]::Max(0.0, $X2 - $X1)
        H = [Math]::Max(0.0, $Y2 - $Y1)
        Cx = ($X1 + $X2) / 2.0
        Cy = ($Y1 + $Y2) / 2.0
        Area = [Math]::Max(0.0, $X2 - $X1) * [Math]::Max(0.0, $Y2 - $Y1)
    }
}

function Clip-Box {
    param(
        $Box,
        [double]$Width,
        [double]$Height
    )
    $x1 = [Math]::Min([Math]::Max($Box.X1, 0.0), $Width)
    $y1 = [Math]::Min([Math]::Max($Box.Y1, 0.0), $Height)
    $x2 = [Math]::Min([Math]::Max($Box.X2, 0.0), $Width)
    $y2 = [Math]::Min([Math]::Max($Box.Y2, 0.0), $Height)
    if ($x2 -le $x1 -or $y2 -le $y1) {
        return $null
    }
    return New-Box -X1 $x1 -Y1 $y1 -X2 $x2 -Y2 $y2
}

function Find-Image {
    param(
        [string]$ImageDir,
        [string]$Stem
    )
    foreach ($ext in $imageExts) {
        $path = Join-Path $ImageDir ($Stem + $ext)
        if (Test-Path $path) {
            return $path
        }
    }
    return $null
}

function Load-Boxes {
    param(
        [string]$LabelPath,
        [int]$ImageWidth,
        [int]$ImageHeight
    )
    $boxes = @()
    foreach ($line in Get-Content $LabelPath) {
        $text = $line.Trim()
        if (-not $text) {
            continue
        }
        $parts = $text -split "\s+"
        if ($parts.Length -lt 5) {
            continue
        }
        $xc = [double]$parts[1] * $ImageWidth
        $yc = [double]$parts[2] * $ImageHeight
        $w = [double]$parts[3] * $ImageWidth
        $h = [double]$parts[4] * $ImageHeight
        $box = New-Box -X1 ($xc - $w / 2.0) -Y1 ($yc - $h / 2.0) -X2 ($xc + $w / 2.0) -Y2 ($yc + $h / 2.0)
        $clipped = Clip-Box -Box $box -Width $ImageWidth -Height $ImageHeight
        if ($null -ne $clipped -and $clipped.Area -gt 1.0) {
            $boxes += $clipped
        }
    }
    return $boxes | Sort-Object Cx, Cy
}

function Should-Pair {
    param($Left, $Right)
    $widthRef = [Math]::Max($Left.W, $Right.W)
    $heightRef = [Math]::Max($Left.H, $Right.H)
    if ($widthRef -le 0 -or $heightRef -le 0) {
        return $false
    }
    $dx = [Math]::Abs($Left.Cx - $Right.Cx)
    $dy = [Math]::Abs($Left.Cy - $Right.Cy)
    $areaRatio = if ($Right.Area -gt 0) { $Left.Area / $Right.Area } else { [double]::PositiveInfinity }
    return (
        $dx -le ($widthRef * 3.2) -and
        $dy -le ($heightRef * 1.2) -and
        $areaRatio -ge 0.4 -and
        $areaRatio -le 2.5
    )
}

function Get-Groups {
    param($Boxes)
    $groups = @()
    $used = @{}
    $candidates = @()
    for ($i = 0; $i -lt $Boxes.Count; $i++) {
        for ($j = $i + 1; $j -lt $Boxes.Count; $j++) {
            if (Should-Pair $Boxes[$i] $Boxes[$j]) {
                $score = [Math]::Abs($Boxes[$i].Cx - $Boxes[$j].Cx) + 0.5 * [Math]::Abs($Boxes[$i].Cy - $Boxes[$j].Cy)
                $candidates += [PSCustomObject]@{ Score = $score; I = $i; J = $j }
            }
        }
    }
    foreach ($candidate in ($candidates | Sort-Object Score)) {
        if ($used.ContainsKey($candidate.I) -or $used.ContainsKey($candidate.J)) {
            continue
        }
        $used[$candidate.I] = $true
        $used[$candidate.J] = $true
        $groups += ,@($candidate.I, $candidate.J)
    }
    for ($i = 0; $i -lt $Boxes.Count; $i++) {
        if (-not $used.ContainsKey($i)) {
            $groups += ,@($i)
        }
    }
    return $groups
}

function Estimate-PersonFromSingle {
    param($Box)
    $personW = [Math]::Max(4.2 * $Box.W, 2.8 * $Box.H)
    $personH = [Math]::Max(7.6 * $Box.H, 3.4 * $Box.W)
    $personCx = $Box.Cx
    $personY2 = $Box.Y2 + 0.08 * $personH
    $personX1 = $personCx - $personW / 2.0
    $personY1 = $personY2 - $personH
    return New-Box -X1 $personX1 -Y1 $personY1 -X2 ($personX1 + $personW) -Y2 $personY2
}

function Estimate-PersonFromPair {
    param($A, $B)
    $ux1 = [Math]::Min($A.X1, $B.X1)
    $uy1 = [Math]::Min($A.Y1, $B.Y1)
    $ux2 = [Math]::Max($A.X2, $B.X2)
    $uy2 = [Math]::Max($A.Y2, $B.Y2)
    $uw = $ux2 - $ux1
    $uh = $uy2 - $uy1
    $personW = [Math]::Max(1.95 * $uw, 2.6 * $uh)
    $personH = [Math]::Max(7.8 * $uh, 2.9 * $uw)
    $personCx = ($ux1 + $ux2) / 2.0
    $personY2 = $uy2 + 0.08 * $personH
    $personX1 = $personCx - $personW / 2.0
    $personY1 = $personY2 - $personH
    return New-Box -X1 $personX1 -Y1 $personY1 -X2 ($personX1 + $personW) -Y2 $personY2
}

function Get-RoiFromPerson {
    param($Person)
    $roiX = $Person.X1 - 0.24 * $Person.W
    $roiY = $Person.Y1 + 0.64 * $Person.H
    $roiW = 1.48 * $Person.W
    $roiH = 0.58 * $Person.H
    $roiX = $roiX - 0.08 * $roiW
    $roiY = $roiY - 0.08 * $roiH
    $roiW = $roiW * 1.16
    $roiH = $roiH * 1.18
    return New-Box -X1 $roiX -Y1 $roiY -X2 ($roiX + $roiW) -Y2 ($roiY + $roiH)
}

function Clamp-Roi {
    param(
        $Roi,
        [int]$ImageWidth,
        [int]$ImageHeight
    )
    $clipped = Clip-Box -Box $Roi -Width $ImageWidth -Height $ImageHeight
    if ($null -eq $clipped) {
        return $null
    }
    $x1 = [Math]::Max(0, [Math]::Min([int][Math]::Floor($clipped.X1), $ImageWidth - 1))
    $y1 = [Math]::Max(0, [Math]::Min([int][Math]::Floor($clipped.Y1), $ImageHeight - 1))
    $x2 = [Math]::Max($x1 + 1, [Math]::Min([int][Math]::Ceiling($clipped.X2), $ImageWidth))
    $y2 = [Math]::Max($y1 + 1, [Math]::Min([int][Math]::Ceiling($clipped.Y2), $ImageHeight))
    return New-Box -X1 $x1 -Y1 $y1 -X2 $x2 -Y2 $y2
}

function Resize-RoiToTarget {
    param(
        $Roi,
        [int]$ImageWidth,
        [int]$ImageHeight,
        [double]$ObjectArea,
        [double]$MinRatio,
        [double]$MaxRatio
    )
    $adjusted = $Roi
    $targetRatio = ($MinRatio + $MaxRatio) / 2.0
    for ($iter = 0; $iter -lt 3; $iter++) {
        $ratio = if ($adjusted.Area -gt 0) { $ObjectArea / $adjusted.Area } else { 0.0 }
        if ($ratio -ge $MinRatio -and $ratio -le $MaxRatio) {
            return $adjusted
        }
        $scale = [Math]::Sqrt($ratio / $targetRatio)
        if ($ratio -lt $MinRatio) {
            $scale = [Math]::Max(0.6, [Math]::Min(0.95, $scale))
        }
        else {
            $scale = [Math]::Min(1.8, [Math]::Max(1.05, $scale))
        }
        $newW = $adjusted.W * $scale
        $newH = $adjusted.H * $scale
        $cx = $adjusted.Cx
        $cy = $adjusted.Cy
        $adjusted = Clamp-Roi (New-Box -X1 ($cx - $newW / 2.0) -Y1 ($cy - $newH / 2.0) -X2 ($cx + $newW / 2.0) -Y2 ($cy + $newH / 2.0)) $ImageWidth $ImageHeight
        if ($null -eq $adjusted) {
            return $null
        }
    }
    return $adjusted
}

function Draw-Rect {
    param(
        [System.Drawing.Graphics]$Graphics,
        [System.Drawing.Pen]$Pen,
        $Box
    )
    $Graphics.DrawRectangle($Pen, [float]$Box.X1, [float]$Box.Y1, [float]$Box.W, [float]$Box.H)
}

$samples = @(
    @{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "00015a7cf95ec19d"; Label = "openimages_single" },
    @{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "0036655159bdef7f"; Label = "openimages_pair" },
    @{ Dataset = "datasets\openimages-shoes-yolo"; Split = "train"; Stem = "00003223e04e2e66"; Label = "openimages_mixed" },
    @{ Dataset = "datasets\ppe-shoes"; Split = "train"; Stem = "image1001"; Label = "ppe_pair_a" },
    @{ Dataset = "datasets\ppe-shoes"; Split = "train"; Stem = "image1011"; Label = "ppe_pair_b" }
)

$summary = @()

foreach ($sample in $samples) {
    $imageDir = Join-Path $repo (Join-Path $sample.Dataset ("images\" + $sample.Split))
    $labelPath = Join-Path $repo (Join-Path $sample.Dataset ("labels\" + $sample.Split + "\" + $sample.Stem + ".txt"))
    $imagePath = Find-Image -ImageDir $imageDir -Stem $sample.Stem
    if (-not $imagePath -or -not (Test-Path $labelPath)) {
        continue
    }

    $bitmap = [System.Drawing.Bitmap]::new($imagePath)
    try {
        $boxes = Load-Boxes -LabelPath $labelPath -ImageWidth $bitmap.Width -ImageHeight $bitmap.Height
        if ($boxes.Count -eq 0) {
            continue
        }

        $groups = Get-Groups -Boxes $boxes
        $canvas = [System.Drawing.Bitmap]::new($bitmap)
        $graphics = [System.Drawing.Graphics]::FromImage($canvas)
        $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
        $shoePen = [System.Drawing.Pen]::new([System.Drawing.Color]::Lime, 3)
        $roiPen = [System.Drawing.Pen]::new([System.Drawing.Color]::Red, 4)
        $font = [System.Drawing.Font]::new("Arial", 18, [System.Drawing.FontStyle]::Bold)
        $brush = [System.Drawing.SolidBrush]::new([System.Drawing.Color]::Yellow)

        try {
            for ($i = 0; $i -lt $boxes.Count; $i++) {
                Draw-Rect -Graphics $graphics -Pen $shoePen -Box $boxes[$i]
            }

            $roiIndex = 0
            foreach ($group in $groups) {
                if ($group.Count -eq 2) {
                    $person = Estimate-PersonFromPair $boxes[$group[0]] $boxes[$group[1]]
                    $roi = Get-RoiFromPerson $person
                    $minRatio = 0.18
                    $maxRatio = 0.40
                    $mode = "pair"
                }
                else {
                    $person = Estimate-PersonFromSingle $boxes[$group[0]]
                    $roi = Get-RoiFromPerson $person
                    $minRatio = 0.10
                    $maxRatio = 0.26
                    $mode = "single"
                }
                $roi = Clamp-Roi $roi $bitmap.Width $bitmap.Height
                if ($null -eq $roi) {
                    continue
                }
                $objectArea = 0.0
                foreach ($idx in $group) {
                    $objectArea += $boxes[$idx].Area
                }
                $roi = Resize-RoiToTarget $roi $bitmap.Width $bitmap.Height $objectArea $minRatio $maxRatio
                if ($null -eq $roi) {
                    continue
                }

                Draw-Rect -Graphics $graphics -Pen $roiPen -Box $roi
                $graphics.DrawString("ROI$roiIndex", $font, $brush, [float]($roi.X1 + 4), [float]([Math]::Max(0, $roi.Y1 - 28)))

                $cropRect = [System.Drawing.Rectangle]::new([int]$roi.X1, [int]$roi.Y1, [int]$roi.W, [int]$roi.H)
                $crop = $bitmap.Clone($cropRect, $bitmap.PixelFormat)
                $cropGraphics = [System.Drawing.Graphics]::FromImage($crop)
                $cropPen = [System.Drawing.Pen]::new([System.Drawing.Color]::Lime, 3)
                try {
                    foreach ($box in $boxes) {
                        if ($box.Cx -lt $roi.X1 -or $box.Cx -gt $roi.X2 -or $box.Cy -lt $roi.Y1 -or $box.Cy -gt $roi.Y2) {
                            continue
                        }
                        $local = New-Box -X1 ($box.X1 - $roi.X1) -Y1 ($box.Y1 - $roi.Y1) -X2 ($box.X2 - $roi.X1) -Y2 ($box.Y2 - $roi.Y1)
                        $local = Clip-Box -Box $local -Width $roi.W -Height $roi.H
                        if ($null -ne $local) {
                            Draw-Rect -Graphics $cropGraphics -Pen $cropPen -Box $local
                        }
                    }
                }
                finally {
                    $cropGraphics.Dispose()
                    $cropPen.Dispose()
                }

                $cropPath = Join-Path $outputDir ($sample.Label + "_roi" + $roiIndex + ".jpg")
                $crop.Save($cropPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
                $crop.Dispose()

                $areaRatio = [Math]::Round($objectArea / $roi.Area, 3)
                $summary += [PSCustomObject]@{
                    Sample = $sample.Label
                    Roi = "roi$roiIndex"
                    Mode = $mode
                    Boxes = $group.Count
                    AreaRatio = $areaRatio
                    Crop = $cropPath
                }
                $roiIndex++
            }

            $overlayPath = Join-Path $outputDir ($sample.Label + "_overlay.jpg")
            $canvas.Save($overlayPath, [System.Drawing.Imaging.ImageFormat]::Jpeg)
        }
        finally {
            $graphics.Dispose()
            $shoePen.Dispose()
            $roiPen.Dispose()
            $font.Dispose()
            $brush.Dispose()
            $canvas.Dispose()
        }
    }
    finally {
        $bitmap.Dispose()
    }
}

$summaryPath = Join-Path $outputDir "summary.txt"
$summary | Sort-Object Sample, Roi | Format-Table -AutoSize | Out-String | Set-Content -Path $summaryPath -Encoding UTF8
Write-Output "Preview images written to: $outputDir"
Write-Output "Summary written to: $summaryPath"
