public/core/Import-MtMaesterResult.ps1

function Import-MtMaesterResult {
    <#
     .Synopsis
      Imports Maester test result JSON files from disk into PowerShell objects.

     .Description
        Loads one or more Maester test result JSON files and returns them as an array
        of single-tenant MaesterResults objects. This is the standard way to load
        previously saved results for use with Merge-MtMaesterResult, Compare-MtTestResult,
        or any future command that operates on result objects.

        If a loaded JSON file contains a multi-tenant merged format (i.e. a "Tenants"
        array from a prior Merge-MtMaesterResult call), each tenant is automatically
        expanded into a separate result object.

        Accepts file paths, glob patterns, or directory paths. When a directory is
        provided, it auto-discovers TestResults-*.json files inside it.

     .Parameter Path
        One or more paths to JSON result files, glob patterns, or directories.
        - File path: ./production.json
        - Glob: ./results/*.json
        - Directory: ./results/ (discovers TestResults-*.json inside)

     .Example
        # Load a single result file
        Import-MtMaesterResult -Path ./production.json

     .Example
        # Load all JSON files matching a glob
        Import-MtMaesterResult -Path ./results/*.json

     .Example
        # Load from a directory (auto-discovers TestResults-*.json)
        Import-MtMaesterResult -Path ./test-results/

     .Example
        # Pipe into Merge for a multi-tenant report
        Import-MtMaesterResult -Path *.json | Merge-MtMaesterResult | Get-MtHtmlReport | Out-File report.html

     .LINK
        https://maester.dev/docs/commands/Import-MtMaesterResult
    #>

    [CmdletBinding()]
    [OutputType([object[]])]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FullName')]
        [string[]] $Path
    )

    begin {
        # Required properties that every single-tenant result must have.
        # This is the same validation used in Compare-MtTestResult.
        $requiredProperties = @('Tests', 'TenantId', 'ExecutedAt')

        $allResults = [System.Collections.Generic.List[psobject]]::new()
    }

    process {
        foreach ($inputPath in $Path) {
            # Resolve the path - handles globs, relative paths, etc.
            $resolvedPaths = @()
            try {
                $resolvedPaths = @(Resolve-Path -Path $inputPath -ErrorAction Stop | Select-Object -ExpandProperty Path)
            } catch {
                Write-Error "Path not found: $inputPath"
                continue
            }

            foreach ($resolved in $resolvedPaths) {
                if (Test-Path -Path $resolved -PathType Container) {
                    # Directory: auto-discover TestResults-*.json files
                    $jsonFiles = @(Get-ChildItem -Path $resolved -Filter 'TestResults-*.json' -File)
                    if ($jsonFiles.Count -eq 0) {
                        # Fall back to any .json file in the directory
                        $jsonFiles = @(Get-ChildItem -Path $resolved -Filter '*.json' -File)
                    }
                    if ($jsonFiles.Count -eq 0) {
                        Write-Warning "No JSON result files found in directory: $resolved"
                        continue
                    }
                    Write-Verbose "Found $($jsonFiles.Count) JSON file(s) in directory: $resolved"
                    foreach ($file in $jsonFiles) {
                        Import-SingleResultFile -FilePath $file.FullName -RequiredProperties $requiredProperties -ResultList $allResults
                    }
                } elseif (Test-Path -Path $resolved -PathType Leaf) {
                    # Single file
                    Import-SingleResultFile -FilePath $resolved -RequiredProperties $requiredProperties -ResultList $allResults
                } else {
                    Write-Error "Path is neither a file nor a directory: $resolved"
                }
            }
        }
    }

    end {
        if ($allResults.Count -eq 0) {
            Write-Warning "No valid Maester result files were loaded."
        } else {
            Write-Verbose "Loaded $($allResults.Count) Maester result(s) in total."
        }
        return , $allResults.ToArray()
    }
}

function Import-SingleResultFile {
    <#
    .Synopsis
        Helper: loads a single JSON file and adds valid results to the list.
        Handles both single-tenant and multi-tenant (merged) formats.
    #>

    [CmdletBinding()]
    param(
        [string] $FilePath,
        [string[]] $RequiredProperties,
        [System.Collections.Generic.List[psobject]] $ResultList
    )

    Write-Verbose "Loading: $FilePath"
    try {
        $content = Get-Content -Path $FilePath -Raw -ErrorAction Stop
        $data = $content | ConvertFrom-Json -ErrorAction Stop
    } catch {
        Write-Warning "Failed to read or parse JSON from '$FilePath': $_"
        return
    }

    # Check if this is a multi-tenant merged format (has Tenants[] array)
    if ($data.PSObject.Properties.Name -contains 'Tenants' -and $null -ne $data.Tenants) {
        Write-Verbose " Detected multi-tenant merged format with $($data.Tenants.Count) tenant(s) in: $FilePath"
        foreach ($tenant in $data.Tenants) {
            if (Test-MaesterResultValid -Result $tenant -RequiredProperties $RequiredProperties -SourceFile $FilePath) {
                $ResultList.Add($tenant)
            }
        }
    } else {
        # Single-tenant format
        if (Test-MaesterResultValid -Result $data -RequiredProperties $RequiredProperties -SourceFile $FilePath) {
            $ResultList.Add($data)
        }
    }
}

function Test-MaesterResultValid {
    <#
    .Synopsis
        Helper: validates that a result object has the required properties.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [psobject] $Result,
        [string[]] $RequiredProperties,
        [string] $SourceFile
    )

    foreach ($prop in $RequiredProperties) {
        if (-not ($Result.PSObject.Properties.Name -contains $prop)) {
            Write-Warning "Result from '$SourceFile' is missing required property '$prop'. Skipping."
            return $false
        }
    }
    return $true
}