. "$PSScriptRoot/Config.ps1";
. "$PSScriptRoot/../Types/OneShotTask.ps1";
. "$PSScriptRoot/../../Windows/Scripts/PowerManagement.ps1";

$null = New-Module {
    . "$PSScriptRoot/../Types/OneShotTask.ps1";
    $logName = "Application";
    $oneShotTrigger = 1337;
    $taskOption = "OneShotTask";
    # ToDo: Store "ProgramData/PortValhalla" path somewhere as const
    $errorPath = "$env:ProgramData/PortValhalla/error.txt";

    $taskSetter = {
        param([Nullable[OneShotTask]] $Task)
        Set-SetupOption $taskOption ([string]$Task);
    };

    function Start-Operation {
        param(
            [scriptblock] $Action
        )

        $Global:ErrorActionPreference = 'Inquire';
        $env:WSLENV = "CONFIG_MODULE/p";

        if ($env:CONFIG_MODULE) {
            $env:CONFIG_MODULE = Resolve-Path $env:CONFIG_MODULE;
        }

        New-Alias -Force "sudo" gsudo;
        & $Action;
    }

    <#
        .SYNOPSIS
        Gets the current OneShot task.
    #>
    function Get-OneShotTask {
        [OneShotTask](Get-SetupOption $taskOption);
    }

    <#
        .SYNOPSIS
        Registers a task for listening to OneShot invocations.
    #>
    function Enable-OneShotListener {
        $tempTask = "PortValhalla Temp";
        $action = New-ScheduledTaskAction -Execute "pwsh" -Argument (Get-StartupArguments);
        schtasks /Create /SC ONEVENT /EC $logName /MO "*[System[Provider[@Name='$logName'] and EventID=$($oneShotTrigger)]]" /TR cmd.exe /TN $tempTask;
        $trigger = (Get-ScheduledTask $tempTask).Triggers;
        $principal = New-ScheduledTaskPrincipal -UserId (Get-SetupUser) -RunLevel Highest;
        $task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger;
        $null = Register-ScheduledTask -Force "PortValhalla OneShot" -InputObject $task;
        $null = Unregister-ScheduledTask -Confirm:$false $tempTask;
    }

    <#
        .SYNOPSIS
        Invokes a one-shot task.

        .PARAMETER Task
        The task to run.
    #>
    function Invoke-OneShot {
        param(
            [OneShotTask] $Task
        )

        $currentStage = Get-Stage;
        Set-Stage ([SetupStage]::OneShot);
        & $taskSetter $Task;

        $job = & {
            $identifier = "EventLog$oneShotTrigger";
            $log = [System.Diagnostics.EventLog]::new($logName);

            $null = Register-ObjectEvent -InputObject $log -EventName EntryWritten -Action {
                $entry = $event.SourceEventArgs.Entry;

                if ($entry.EventID -eq $oneShotTrigger) {
                    $null = New-Event -SourceIdentifier $identifier;
                }
            };

            Start-Job {
                Wait-Event -SourceIdentifier $identifier;
            };
        };

        Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "Starting OneShot task ``$(Get-OneShotTask)``…";
        $null = Wait-Job $job;
        Set-Stage $currentStage;

        if (Test-Path $errorPath) {
            $errorMessage = Get-Content $errorPath;
            Remove-Item $errorPath;
            throw $errorMessage;
        }
    }

    # ToDo: Store Run-OneShot and Receive-OneShot somewhere else in Windows folder

    <#
        .SYNOPSIS
        Executes the specified action and notifies the OneShot task executor.
    #>
    function Start-OneShot {
        param(
            [scriptblock] $Action
        )

        try {
            Start-Operation @PSBoundParameters;
        }
        catch {
            Set-Content -Path $errorPath -Value $Error;
        }
        finally {
            Set-Stage ([SetupStage]::Idle);
            Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished.";
        }
    }
};