src/Public.ps1

function Get-InstalledPSLResource {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter()]
        [switch] $IncludeDependencies
    )

    $projectRoot = Find-ProjectRoot -Path $Path
    $requirementsPath = Get-RequirementsPath -ProjectRoot $projectRoot
    $lockfilePath = Get-LockfilePath -ProjectRoot $projectRoot

    $requirements = Import-PowerShellDataFile -Path $requirementsPath
    if ($requirements -isnot [hashtable]) {
        throw "Requirements file must be a hashtable: $requirementsPath"
    }
    $directNames = [string[]]$requirements.Keys

    $lock = Read-Lockfile -Path $lockfilePath

    $names = [string[]]$lock.Keys
    [System.Array]::Sort($names, [System.StringComparer]::Ordinal)

    $result = [System.Collections.Generic.List[object]]::new()
    foreach ($name in $names) {
        $entry = $lock[$name]
        if ($entry -isnot [hashtable]) {
            throw "Lockfile entry must be a hashtable for resource '$name': $lockfilePath"
        }

        $version = $entry['Version']
        $prerelease = $entry['Prerelease']
        if ($prerelease -isnot [string]) {
            $prerelease = $null
        }
        $repository = $entry['Repository']

        $isDirect = $directNames -contains $name
        if ($IncludeDependencies -or $isDirect) {
            $result.Add((New-Resource -Name $name -Version $version -Prerelease $prerelease -Repository $repository -IsDirect $isDirect -ProjectRoot $projectRoot))
        }
    }

    $result.ToArray()
}

function Install-PSLResource {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter()]
        [switch] $IncludeDependencies
    )

    $projectRoot = Find-ProjectRoot -Path $Path
    if (-not $PSCmdlet.ShouldProcess($projectRoot, 'Install project-local resources')) {
        return
    }

    Invoke-InstallOrUpdateCore -ProjectRoot $projectRoot -Operation 'Install' -IncludeDependencies ([bool]$IncludeDependencies)
}

function Update-PSLResource {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter()]
        [switch] $IncludeDependencies
    )

    $projectRoot = Find-ProjectRoot -Path $Path
    if (-not $PSCmdlet.ShouldProcess($projectRoot, 'Update project-local resources')) {
        return
    }

    Invoke-InstallOrUpdateCore -ProjectRoot $projectRoot -Operation 'Update' -IncludeDependencies ([bool]$IncludeDependencies)
}

function Uninstall-PSLResource {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]] $Name
    )

    $projectRoot = Find-ProjectRoot -Path $Path
    $requirementsPath = Get-RequirementsPath -ProjectRoot $projectRoot
    $lockfilePath = Get-LockfilePath -ProjectRoot $projectRoot
    $storePath = Get-StorePath -ProjectRoot $projectRoot

    $requirements = Import-PowerShellDataFile -Path $requirementsPath
    if ($requirements -isnot [hashtable]) {
        throw "Requirements file must be a hashtable: $requirementsPath"
    }
    Assert-RequirementsAreSupported -Requirements $requirements -RequirementsPath $requirementsPath

    foreach ($resourceName in $Name) {
        if ([string]::IsNullOrWhiteSpace($resourceName)) {
            throw 'Name must not contain empty values.'
        }
        if (-not $requirements.ContainsKey($resourceName)) {
            throw "Requirement not found for resource '$resourceName': $requirementsPath"
        }
    }

    if (-not $PSCmdlet.ShouldProcess($projectRoot, 'Uninstall project-local resources')) {
        return
    }

    foreach ($resourceName in $Name) {
        $requirements.Remove($resourceName) | Out-Null
    }

    Write-PowerShellDataFile -Path $requirementsPath -Data $requirements

    if (Test-Path -LiteralPath $storePath) {
        if (Test-Path -LiteralPath $storePath -PathType Leaf) {
            throw "Store path must be a directory: $storePath"
        }
        Remove-Item -LiteralPath $storePath -Recurse -Force
    }

    if ($requirements.Count -eq 0) {
        $emptyLockData = @{}
        Write-Lockfile -Path $lockfilePath -Data $emptyLockData
        return @()
    }

    $resolved = Resolve-RequirementsToLockData -Requirements $requirements -RequirementsPath $requirementsPath -StorePath $storePath
    $directNames = [string[]]$resolved['DirectNames']
    $lockData = [hashtable]$resolved['LockData']

    Write-Lockfile -Path $lockfilePath -Data $lockData

    ConvertTo-PSLRMResourcesFromLockData -LockData $lockData -DirectNames $directNames -IncludeDependencies $false -ProjectRoot $projectRoot
}

function Restore-PSLResource {
    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([object])]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter()]
        [switch] $IncludeDependencies
    )

    $projectRoot = Find-ProjectRoot -Path $Path
    $requirementsPath = Get-RequirementsPath -ProjectRoot $projectRoot
    $lockfilePath = Get-LockfilePath -ProjectRoot $projectRoot
    $storePath = Get-StorePath -ProjectRoot $projectRoot

    $requirements = Import-PowerShellDataFile -Path $requirementsPath
    if ($requirements -isnot [hashtable]) {
        throw "Requirements file must be a hashtable: $requirementsPath"
    }
    Assert-RequirementsAreSupported -Requirements $requirements -RequirementsPath $requirementsPath
    $directNames = [string[]]$requirements.Keys

    $lockData = Read-Lockfile -Path $lockfilePath

    if (-not $PSCmdlet.ShouldProcess($projectRoot, 'Restore project-local resources from lockfile')) {
        return
    }

    if (Test-Path -LiteralPath $storePath) {
        if (Test-Path -LiteralPath $storePath -PathType Leaf) {
            throw "Store path must be a directory: $storePath"
        }
        Remove-Item -LiteralPath $storePath -Recurse -Force
    }

    Save-LockDataToStore -LockData $lockData -StorePath $storePath

    ConvertTo-PSLRMResourcesFromLockData -LockData $lockData -DirectNames $directNames -IncludeDependencies ([bool]$IncludeDependencies) -ProjectRoot $projectRoot
}

function Invoke-PSLResource {
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string] $CommandName,

        [Parameter()]
        [Alias('Arguments')]
        [AllowNull()]
        [object[]] $ArgumentTokens,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = (Get-Location).Path,

        [Parameter()]
        [ValidateSet('IsolatedRunspace', 'InProcess')]
        [string] $ExecutionScope = 'IsolatedRunspace'
    )

    $projectRoot = Find-ProjectRoot -Path $Path

    if ($ExecutionScope -eq 'InProcess') {
        throw "ExecutionScope 'InProcess' is not implemented yet. Use 'IsolatedRunspace'."
    }

    try {
        Invoke-InIsolatedRunspace -ProjectRoot $projectRoot -CommandName $CommandName -ArgumentTokens $ArgumentTokens
    }
    catch {
        $PSCmdlet.ThrowTerminatingError($_)
    }
}