using namespace Microsoft.Win32;
using namespace System.Management.Automation.Host;

$null = New-Module {
    . "$PSScriptRoot/SoftwareManagement.ps1";
    . "$PSScriptRoot/../Windows/lib/Registry.ps1";
    . "$PSScriptRoot/../Windows/lib/WSL.ps1";
    . "$PSScriptRoot/../Windows/Types/WindowsInstallerAction.ps1";

    <#
        .SYNOPSIS
        Prompts the user to select a profile to act on.
    #>
    function Show-ProfileNamePrompt {

        $profiles = & {
            if (-not $IsWindows -or (Test-Command "wsl")) {
                return Invoke-ConfigScript "getProfiles";
            }
            else {
                return Get-ChildItem "$PSScriptRoot/../../.config" | ForEach-Object { Split-Path -LeafBase $_ };
            }
        };

        $choice = $Host.UI.PromptForChoice(
            "Select Profile",
            (& {
                if ($IsWindows) {
                    switch (Get-Stage) {
                        ([WindowsInstallerAction]::Backup) {
                            "Which profile do you wish to back up?";
                        }
                        ([WindowsInstallerAction]::Install) {
                            "Which profile do you wish to install?";
                        }
                        $null {
                            "Which profile do you wish to set up?";
                        }
                    }
                }
                else {
                    "Please select a profile:";
                }
            }),
            (& {
                for ($i = 0; $i -lt $profiles.Count; $i++) {
                    [ChoiceDescription]"&$i - $($profiles[$i])";
                }

                [ChoiceDescription]"&Abort";
            }),
            $profiles.Count);

        if ($choice -eq $profiles.Count) {
            exit;
        }
        else {
            $env:CONFIG_NAME = $profiles[$choice];
        }
    }

    <#
        .SYNOPSIS
        Runs a script based on the `settings.fish` script.

        .PARAMETER Script
        The script to run.
    #>
    function Invoke-ConfigScript {
        param(
            [string] $Script
        )

        $scriptPath = "$PSScriptRoot/settings.fish";

        if ($env:CONFIG_NAME -or ($Script -eq "getProfiles")) {
            $output = & {
                if (-not $IsWindows) {
                    $escapedPath = (fish -c 'string escape $argv' "$scriptPath");
                    fish -c ". $escapedPath; $Script";
                }
                else {
                    if (-not $env:VALHALLA_FLAKE_ROOT) {
                        $cleanup = { };
                        $projectRoot = "$PSScriptRoot/../..";
                        $archisoDir = "$projectRoot/archiso";

                        if (Test-Path -PathType Container "$archisoDir") {
                            $git = {
                                git -C "$projectRoot" -c safe.directory="$("$(Resolve-Path $projectRoot)".Replace("\", "/"))" @args;
                            };

                            & $git rm -r --cached "$archisoDir" *> $null;
                            $cleanup = { & $git restore --staged "$archisoDir" };
                        }

                        $env:VALHALLA_FLAKE_ROOT = (Invoke-Nix flake metadata (ConvertTo-LinuxPath "$PSScriptRoot/../..") --json | ConvertFrom-Json).path;
                        & $cleanup *> $null;
                    }

                    $output = Invoke-Fish -c ". $(ConvertTo-LinuxPath $scriptPath); $Script";

                    if (-not $?) {
                        Write-Error "The configuration could not be retrieved!";
                    }
                    else {
                        $output;
                    }
                }
            };

            if (-not ($output -and ($output | Test-Json))) {
                Write-Error "The value ``$output`` is not valid JSON.";
            }
            else {
                $output | ConvertFrom-Json;
            }
        }
        else {
            $null;
        }
    }

    <#
        .SYNOPSIS
        Gets a configuration option.

        .PARAMETER Name
        The name of the configuration value to get.

        .PARAMETER ArgumentList
        The arguments to send to the configuration script.
    #>
    function Get-Config {
        param(
            [string] $Name,
            [Parameter(ValueFromRemainingArguments)]
            [string[]] $ArgumentList
        )

        Invoke-ConfigScript "getConfig $Name --json $ArgumentList";
    }

    <#
        .SYNOPSIS
        Gets the name of the config root.
    #>
    function Get-OSConfigRoot {
        return "valhalla.$($IsWindows ? "windows" : "linux")";
    }

    <#
        .SYNOPSIS
        Gets the configuration value for the current operating system.

        .PARAMETER Name
        The name of the configuration value to get.

        .PARAMETER ArgumentList
        The arguments to send to the configuration script.
    #>
    function Get-OSConfig {
        param(
            [string] $Name,
            [Parameter(ValueFromRemainingArguments)]
            [string[]] $ArgumentList
        )

        return Get-Config -Name "$(Get-OSConfigRoot).$Name" @PSBoundParameters;
    }

    <#
        .SYNOPSIS
        Gets the configuration of the specified program.

        .PARAMETER Name
        The name of the program to get the configuration for.
    #>
    function Get-ProgramConfig {
        param(
            $User,
            [Parameter(Position = 0)]
            $Name
        )

        $programs = & {
            if ($User) {
                return Get-UserConfig -UserName $User @args;
            } else {
                return Get-OSConfig @args;
            }
        } "programs";

        return $programs.$Name;
    }

    <#
        .SYNOPSIS
        Tests whether the program 
    #>
    function Test-Program {
        param(
            $User,
            [Parameter(Position = 0)]
            $Name
        )

        try {
            (Get-ProgramConfig @PSBoundParameters).enable;
        } catch {
            $false;
        }
    }

    <#
        .SYNOPSIS
        Gets the name of the user root.
    #>
    function Get-UserConfigRoot {
        return "$(Get-OSConfigRoot).$($IsWindows ? "winUsers" : "users")";
    }

    <#
        .SYNOPSIS
        Gets a user configuration.

        .PARAMETER UserName
        The name of the user to get the configuration for.

        .PARAMETER Name
        The name of the configuration to get.
    #>
    function Get-UserConfig {
        param(
            [string] $UserName = ($IsWindows ? $env:UserName : $env:USER),
            [Parameter(Mandatory, Position = 0)]
            [string] $Name
        )

        if ((Get-Users) -contains $UserName) {
            Get-Config "$(Get-UserConfigRoot).$UserName.$Name";
        }
        else {
            return $null;
        }
    }

    <#
        .SYNOPSIS
        Gets the attributes of a configuration object.

        .PARAMETER Name
        The name of the configuration to get the attributes of.
    #>
    function Get-Attributes {
        param(
            [string] $Name
        )

        Invoke-ConfigScript "getAttributes $Name";
    }

    <#
        .SYNOPSIS
        Gets the names of the users to create.
    #>
    function Get-Users {
        [OutputType([string[]])]
        param()
        Get-Attributes "$(Get-UserConfigRoot)";
    }

    <#
        .SYNOPSIS
        Gets the name of the setup user.
    #>
    function Get-SetupUser {
        [OutputType([string])]
        param()
        Get-OSConfig "setupUser.name";
    }
}