. "$PSScriptRoot/Config.ps1";
. "$PSScriptRoot/Operations.ps1";
. "$PSScriptRoot/System.ps1";
. "$PSScriptRoot/../Types/InstallerAction.ps1";

$null = New-Module {
    . "$PSScriptRoot/../Types/InstallerAction.ps1";
    $userArgument = "name";

    function Start-SoftwareInstaller {
        param(
            [string] $Name,
            [switch] $Force,
            [scriptblock] $Base = $null,
            [scriptblock] $Backup = $null,
            [scriptblock] $Installer = $null,
            [scriptblock] $Configurator = $null,
            [scriptblock] $UserBackup = $null,
            [scriptblock] $UserConfigurator = $null,
            [string] $Inherit,
            [Parameter(Position = 0)]
            [Nullable[InstallerAction]] $Action,
            [Parameter(Position = 1)]
            [hashtable] $Arguments,
            [Parameter()]
            [hashtable] $Context
        )

        [InstallerAction] $Action = & {
            if ($null -ne $Action) {
                $Action;
            }
            else {
                [InstallerAction]::Install;
            }
        };

        $runBase = {
            $defaults = @{ action = $Action };
            & "$Inherit" @defaults @args;
        };

        if (-not $Name) {
            $Name = Split-Path -Leaf (Split-Path -Parent ((Get-PSCallStack)[1].ScriptName));
        }

        if ($Inherit) {
            foreach ($script in @("Backup", "Installer", "Configurator", "UserBackup", "UserConfigurator")) {
                if (-not (Get-Variable $script).Value) {
                    Set-Variable $script {
                        param([switch]$Force)
                        $parameters = $MyInvocation.UnboundArguments;

                        if ($Force.ToBool()) {
                            $parameters = $parameters + @("-Force");
                        }

                        & $runBase @parameters
                    };
                }
            }
        }

        Start-Operation {
            $installHandler = {
                param(
                    [string] $Name,
                    [switch] $Force,
                    [InstallerAction] $Action,
                    [hashtable] $Arguments,
                    [hashtable] $Context
                )

                [string] $DisplayName = $null;

                if ($null -ne $Name) {
                    $DisplayName = "``$Name``";
                }
                else {
                    $DisplayName = "unknown software";
                }

                $Arguments ??= @{ };
                $Context ??= @{ };
                $install = $Force -or (Test-Program "$Name");

                $argumentList = @{
                    name      = $Name;
                    force     = $Force;
                    installer = $installHandler;
                    arguments = $Arguments;
                    context   = $Context;
                    base = { & $runBase @args };
                };

                switch ($Action) {
                    ([InstallerAction]::Backup) {
                        if ($Backup -and $install) {
                            Write-Host "Backing up $DisplayName…";
                            & $Backup @argumentList;
                        }

                        break;
                    }
                    ([InstallerAction]::Install) {
                        if ($Installer -and $install) {
                            Write-Host "Installing $DisplayName…";
                            & $Installer @argumentList;
                        }

                        & $installHandler @argumentList -Action ([InstallerAction]::Configure);

                        if ($UserConfigurator -and (-not (Test-SetupUser))) {
                            & $installHandler @argumentList -Action ([InstallerAction]::ConfigureUser);
                        }

                        break;
                    }
                    ([InstallerAction]::Configure) {
                        if ($Configurator -and $install) {
                            Write-Host "Configuring $DisplayName…";
                            & $Configurator @argumentList;
                        }

                        break;
                    }
                    default {
                        if ((-not $Arguments.ContainsKey($userArgument)) -or (-not $Arguments[$userArgument])) {
                            $Arguments.Remove($userArgument);
                            $Arguments.Add($userArgument, ($IsWindows ? $env:UserName : $env:USER));
                        }

                        $user = $Arguments[$userArgument];
                        $install = $Force -or (Test-Program -User $user "$Name");

                        switch ($_) {
                            ([InstallerAction]::BackupUser) {
                                if ($UserBackup -and $install) {
                                    Write-Host "Backing up $DisplayName for user ``$user``…";
                                    & $UserBackup @argumentList;
                                }
                            }
                            ([InstallerAction]::ConfigureUser) {
                                if ($UserConfigurator -and $install) {
                                    Write-Host "Configuring $DisplayName for user ``$user``…";
                                    & $UserConfigurator @argumentList;
                                }
                            }
                        }

                        break;
                    }
                }
            };

            & $installHandler -Name $Name -Force:$($Force.ToBool()) -Action $Action -Arguments $Arguments -Context $Context;
        };
    }
}