param(
    [Parameter(Mandatory = $true)]
    [string]$TargetSignaturePath,

    [Parameter(Mandatory = $true)]
    [string]$CandidateExtractionRoot,

    [string]$CandidateViewsJson = "",

    [string]$OutputPath = "",

    [double]$PrimitiveWeight = 0.15,
    [double]$DirectionWeight = 0.15,
    [double]$LineLengthWeight = 0.15,
    [double]$CircleRadiusWeight = 0.03,
    [double]$ArcRadiusWeight = 0.03,
    [double]$ArcLengthWeight = 0.04,
    [double]$RelativeMidpointWeight = 0.30,
    [double]$RelativeFeatureWeight = 0.15
)

$ErrorActionPreference = "Stop"

function Read-JsonFile([string]$Path) {
    if (-not (Test-Path -LiteralPath $Path)) {
        throw "File does not exist: $Path"
    }
    return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
}

function Get-PropertyValue($Object, [string]$Name) {
    if ($null -eq $Object) {
        return $null
    }
    $property = $Object.PSObject.Properties[$Name]
    if ($null -eq $property) {
        return $null
    }
    return $property.Value
}

function Convert-ToNumberMap($Object) {
    $result = [ordered]@{}
    if ($null -eq $Object) {
        return $result
    }

    foreach ($property in $Object.PSObject.Properties) {
        $value = 0.0
        if ($null -ne $property.Value) {
            $value = [double]$property.Value
        }
        $result[$property.Name] = $value
    }
    return $result
}

function Get-Map($Signature, [string]$Name) {
    return Convert-ToNumberMap (Get-PropertyValue $Signature $Name)
}

function Get-RelativeMap($Signature, [string]$Name) {
    $relative = Get-PropertyValue $Signature "relative_line_coordinates"
    return Convert-ToNumberMap (Get-PropertyValue $relative $Name)
}

function Get-MapSum($Map) {
    $sum = 0.0
    foreach ($key in $Map.Keys) {
        $sum += [double]$Map[$key]
    }
    return $sum
}

function Get-HistogramSimilarity($CandidateMap, $TargetMap) {
    $keys = @($CandidateMap.Keys + $TargetMap.Keys | Select-Object -Unique)
    if ($keys.Count -eq 0) {
        return 1.0
    }

    $intersection = 0.0
    $union = 0.0
    foreach ($key in $keys) {
        $candidateValue = 0.0
        $targetValue = 0.0
        if ($CandidateMap.Contains($key)) {
            $candidateValue = [double]$CandidateMap[$key]
        }
        if ($TargetMap.Contains($key)) {
            $targetValue = [double]$TargetMap[$key]
        }
        $intersection += [Math]::Min($candidateValue, $targetValue)
        $union += [Math]::Max($candidateValue, $targetValue)
    }

    if ($union -le 0.0) {
        return 1.0
    }
    return $intersection / $union
}

function Get-CountSimilarity([double]$CandidateCount, [double]$TargetCount) {
    $maxValue = [Math]::Max($CandidateCount, $TargetCount)
    if ($maxValue -le 0.0) {
        return 1.0
    }
    return [Math]::Min($CandidateCount, $TargetCount) / $maxValue
}

function Get-CandidateMetadataMap([string]$Path) {
    $map = @{}
    if ([string]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path)) {
        return $map
    }

    $rawItems = Read-JsonFile $Path
    $items = if ($rawItems -is [array]) { $rawItems } else { @($rawItems) }
    foreach ($item in $items) {
        $index = [int](Get-PropertyValue $item "index")
        $map[[string]$index] = $item
    }
    return $map
}

$target = Read-JsonFile $TargetSignaturePath
$candidateRoot = Resolve-Path -LiteralPath $CandidateExtractionRoot
if ([string]::IsNullOrWhiteSpace($OutputPath)) {
    $OutputPath = Join-Path $candidateRoot.Path "candidate-ranking-relative.json"
}

$weights = [ordered]@{
    primitive_counts = $PrimitiveWeight
    line_direction_counts = $DirectionWeight
    line_length_histogram = $LineLengthWeight
    circle_radius_histogram = $CircleRadiusWeight
    arc_radius_histogram = $ArcRadiusWeight
    arc_length_histogram = $ArcLengthWeight
    relative_midpoint_histogram = $RelativeMidpointWeight
    relative_feature_histogram = $RelativeFeatureWeight
}

$weightSum = 0.0
foreach ($value in $weights.Values) {
    $weightSum += [double]$value
}
if ([Math]::Abs($weightSum - 1.0) -gt 0.000001) {
    throw "Weights must sum to 1.0. Current sum: $weightSum"
}

$metadata = Get-CandidateMetadataMap $CandidateViewsJson
$targetMaps = @{
    primitive_counts = Get-Map $target "primitive_counts"
    line_direction_counts = Get-Map $target "line_direction_counts"
    line_length_histogram = Get-Map $target "line_length_histogram"
    circle_radius_histogram = Get-Map $target "circle_radius_histogram"
    arc_radius_histogram = Get-Map $target "arc_radius_histogram"
    arc_length_histogram = Get-Map $target "arc_length_histogram"
    relative_midpoint_histogram = Get-RelativeMap $target "relative_midpoint_histogram"
    relative_feature_histogram = Get-RelativeMap $target "feature_histogram"
}

$signatureFiles = @(Get-ChildItem -LiteralPath $candidateRoot.Path -Recurse -Filter "main-view-signature.json" | Sort-Object FullName)
if ($signatureFiles.Count -eq 0) {
    throw "No candidate signatures found under: $($candidateRoot.Path)"
}

$ranking = @()
foreach ($signatureFile in $signatureFiles) {
    $candidate = Read-JsonFile $signatureFile.FullName
    $directoryName = Split-Path -Leaf (Split-Path -Parent $signatureFile.FullName)
    $index = $null
    if ($directoryName -match '^(\d+)_') {
        $index = [int]$Matches[1]
    }

    $candidateMaps = @{
        primitive_counts = Get-Map $candidate "primitive_counts"
        line_direction_counts = Get-Map $candidate "line_direction_counts"
        line_length_histogram = Get-Map $candidate "line_length_histogram"
        circle_radius_histogram = Get-Map $candidate "circle_radius_histogram"
        arc_radius_histogram = Get-Map $candidate "arc_radius_histogram"
        arc_length_histogram = Get-Map $candidate "arc_length_histogram"
        relative_midpoint_histogram = Get-RelativeMap $candidate "relative_midpoint_histogram"
        relative_feature_histogram = Get-RelativeMap $candidate "feature_histogram"
    }

    $componentScores = [ordered]@{}
    $score = 0.0
    foreach ($key in $weights.Keys) {
        $component = Get-HistogramSimilarity $candidateMaps[$key] $targetMaps[$key]
        $componentScores[$key] = $component
        $score += $component * [double]$weights[$key]
    }

    $candidatePrimitiveCounts = $candidateMaps["primitive_counts"]
    $targetPrimitiveCounts = $targetMaps["primitive_counts"]
    $candidateLineCount = if ($candidatePrimitiveCounts.Contains("line")) { [double]$candidatePrimitiveCounts["line"] } else { 0.0 }
    $targetLineCount = if ($targetPrimitiveCounts.Contains("line")) { [double]$targetPrimitiveCounts["line"] } else { 0.0 }

    $meta = $null
    if ($null -ne $index -and $metadata.ContainsKey([string]$index)) {
        $meta = $metadata[[string]$index]
    }

    $ranking += [pscustomobject][ordered]@{
        rank = 0
        index = $index
        name = $directoryName
        candidate_view_name = Get-PropertyValue $meta "name"
        direction_name = Get-PropertyValue $meta "direction_name"
        roll_degrees = Get-PropertyValue $meta "roll_deg"
        score = $score
        component_scores = $componentScores
        candidate_line_count = $candidateLineCount
        target_line_count = $targetLineCount
        line_count_score = Get-CountSimilarity $candidateLineCount $targetLineCount
        candidate_relative_midpoint_bins = (Get-MapSum $candidateMaps["relative_midpoint_histogram"])
        target_relative_midpoint_bins = (Get-MapSum $targetMaps["relative_midpoint_histogram"])
        signature_path = $signatureFile.FullName
    }
}

$sorted = @($ranking | Sort-Object -Property @{ Expression = { [double]$_.score }; Descending = $true }, @{ Expression = { [int]$_.index }; Ascending = $true })
for ($i = 0; $i -lt $sorted.Count; $i++) {
    $sorted[$i].rank = $i + 1
}

$result = [ordered]@{
    ok = $true
    created_at = (Get-Date).ToString("o")
    target_signature_path = (Resolve-Path -LiteralPath $TargetSignaturePath).Path
    candidate_extraction_root = $candidateRoot.Path
    candidate_count = $sorted.Count
    weights = $weights
    ranking = $sorted
}

$outputDirectory = Split-Path -Parent $OutputPath
if (-not [string]::IsNullOrWhiteSpace($outputDirectory)) {
    New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
}
$result | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath $OutputPath -Encoding UTF8

$sorted | Select-Object -First 10 rank,index,name,score,candidate_line_count,target_line_count,line_count_score | Format-Table -AutoSize
Write-Host "Ranking written: $OutputPath"
