#Requires -Version 5.1

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

function Get-RootPrefix {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Root
    )

    if ($Root.EndsWith([IO.Path]::DirectorySeparatorChar) -or
        $Root.EndsWith([IO.Path]::AltDirectorySeparatorChar)) {
        return $Root
    }

    return $Root + [IO.Path]::DirectorySeparatorChar
}

function Test-IsUnderRoot {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Root,

        [Parameter(Mandatory = $true)]
        [string] $Candidate
    )

    $prefix = Get-RootPrefix -Root $Root
    return $Candidate.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)
}

function Find-ReparseAncestor {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Root,

        [Parameter(Mandatory = $true)]
        [string] $Candidate
    )

    $rootItem = Get-Item -LiteralPath $Root -Force
    if (($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
        return '.'
    }

    $prefix = Get-RootPrefix -Root $Root
    $relative = $Candidate.Substring($prefix.Length)
    $current = $Root
    foreach ($part in ($relative -split '[\\/]' | Where-Object { $_.Length -ne 0 })) {
        $current = Join-Path $current $part
        if (-not (Test-Path -LiteralPath $current)) {
            break
        }

        $item = Get-Item -LiteralPath $current -Force
        if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
            return $part
        }
    }

    return $null
}

function Resolve-CoveragePath {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Root,

        [Parameter(Mandatory = $true)]
        [string] $RelativePath
    )

    if ([string]::IsNullOrWhiteSpace($RelativePath) -or
        [IO.Path]::IsPathRooted($RelativePath)) {
        return [pscustomobject][ordered]@{
            Status = 'RootEscape'
            FullPath = $null
        }
    }

    try {
        $candidate = [IO.Path]::GetFullPath((Join-Path $Root $RelativePath))
    }
    catch {
        return [pscustomobject][ordered]@{
            Status = 'ManifestInvalid'
            FullPath = $null
        }
    }

    if (-not (Test-IsUnderRoot -Root $Root -Candidate $candidate)) {
        return [pscustomobject][ordered]@{
            Status = 'RootEscape'
            FullPath = $null
        }
    }

    $reparse = Find-ReparseAncestor -Root $Root -Candidate $candidate
    if (-not [string]::IsNullOrEmpty($reparse)) {
        return [pscustomobject][ordered]@{
            Status = 'Reparse'
            FullPath = $null
        }
    }

    return [pscustomobject][ordered]@{
        Status = 'Resolved'
        FullPath = $candidate
    }
}

function Test-AssetDefinition {
    param(
        [Parameter(Mandatory = $true)]
        [string] $Kind,

        [Parameter(Mandatory = $true)]
        [string] $RelativePath
    )

    $allowedExtensions = switch ($Kind) {
        'Image' { @('.bmp', '.jpeg', '.jpg', '.png', '.tga') }
        'Texture' { @('.bmp', '.jpeg', '.jpg', '.png', '.tga', '.vrv') }
        'Video' { @('.vrv') }
        default { return $false }
    }

    $extension = [IO.Path]::GetExtension($RelativePath)
    return $allowedExtensions -contains $extension.ToLowerInvariant()
}

function Test-LegacyCutCoverageState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string] $CutRoot,

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

        [Parameter(Mandatory = $true)]
        [object[]] $AssetRequirements,

        [Parameter(Mandatory = $true)]
        [int] $ReachableBuilders
    )

    if (-not [IO.Path]::IsPathRooted($CutRoot)) {
        throw 'CutRoot must be an absolute path.'
    }

    $root = [IO.Path]::GetFullPath($CutRoot)
    if (-not (Test-Path -LiteralPath $root -PathType Container)) {
        throw 'CutRoot does not identify an existing directory.'
    }

    $aliasFindings = [Collections.Generic.List[object]]::new()
    foreach ($alias in $Aliases) {
        $relativePath = $alias + '.t2s'
        $resolved = Resolve-CoveragePath -Root $root -RelativePath $relativePath
        $status = $resolved.Status
        if ($status -eq 'Resolved') {
            if (-not (Test-Path -LiteralPath $resolved.FullPath -PathType Leaf)) {
                $status = 'Missing'
            }
            else {
                $item = Get-Item -LiteralPath $resolved.FullPath -Force
                $status = if ($item.Length -le 0) { 'Empty' } else { 'Present' }
            }
        }

        $aliasFindings.Add([pscustomobject][ordered]@{
            Alias = $alias
            RelativePath = $relativePath
            Status = $status
        })
    }

    $assetFindings = [Collections.Generic.List[object]]::new()
    foreach ($requirement in $AssetRequirements) {
        $builder = [string] $requirement.Builder
        $kind = [string] $requirement.Kind
        $relativePath = [string] $requirement.Path
        $status = 'ManifestInvalid'

        if (-not [string]::IsNullOrWhiteSpace($builder) -and
            -not [string]::IsNullOrWhiteSpace($relativePath) -and
            (Test-AssetDefinition -Kind $kind -RelativePath $relativePath)) {
            $resolved = Resolve-CoveragePath -Root $root -RelativePath $relativePath
            $status = $resolved.Status
            if ($status -eq 'Resolved') {
                if (-not (Test-Path -LiteralPath $resolved.FullPath -PathType Leaf)) {
                    $status = 'Missing'
                }
                else {
                    $item = Get-Item -LiteralPath $resolved.FullPath -Force
                    $status = if ($item.Length -le 0) { 'Empty' } else { 'Present' }
                }
            }
        }

        $assetFindings.Add([pscustomobject][ordered]@{
            Builder = $builder
            Kind = $kind
            RelativePath = $relativePath
            Status = $status
        })
    }

    $missingAliases = @($aliasFindings | Where-Object Status -eq 'Missing')
    $unsafeAliases = @($aliasFindings | Where-Object {
        $_.Status -ne 'Present' -and $_.Status -ne 'Missing'
    })
    $missingAssets = @($assetFindings | Where-Object Status -eq 'Missing')
    $unsafeAssets = @($assetFindings | Where-Object {
        $_.Status -ne 'Present' -and $_.Status -ne 'Missing'
    })
    $rootEscapes = @($assetFindings | Where-Object Status -eq 'RootEscape')
    $reparseAssets = @($assetFindings | Where-Object Status -eq 'Reparse')
    $invalidAssets = @($assetFindings | Where-Object Status -eq 'ManifestInvalid')

    $failed = $missingAliases.Count -ne 0 -or
        $unsafeAliases.Count -ne 0 -or
        $missingAssets.Count -ne 0 -or
        $unsafeAssets.Count -ne 0

    return [pscustomobject][ordered]@{
        Status = if ($failed) { 'Failed' } else { 'Passed' }
        ReachableBuilders = $ReachableBuilders
        ActiveAliases = $Aliases.Count
        Missing = $missingAliases.Count
        Unsafe = $unsafeAliases.Count
        RequiredAssets = $AssetRequirements.Count
        MissingAssets = $missingAssets.Count
        UnsafeAssets = $unsafeAssets.Count
        RootEscapes = $rootEscapes.Count
        ReparseAssets = $reparseAssets.Count
        InvalidAssetDefinitions = $invalidAssets.Count
        AliasFindings = $aliasFindings.ToArray()
        AssetFindings = $assetFindings.ToArray()
    }
}

Export-ModuleMember -Function Test-LegacyCutCoverageState
