param(
    [Parameter(ValueFromRemainingArguments = $true)]
    [string[]]$CliArgs
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

$script:logRoot = if ($env:LOCALAPPDATA) { Join-Path $env:LOCALAPPDATA "MonarcLocal" } elseif ($env:TEMP) { $env:TEMP } else { $PWD.Path }
$script:launcherLogFile = Join-Path $script:logRoot "launcher.log"

function Write-LauncherLog {
    param([string]$Message)

    try {
        New-Item -ItemType Directory -Force -Path $script:logRoot | Out-Null
        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"
        Add-Content -Path $script:launcherLogFile -Value "[$timestamp] $Message"
    } catch {
    }
}

function Write-LauncherErrorLog {
    param([string]$ErrorText)

    $logFile = Join-Path $script:logRoot "launcher-error.log"

    try {
        New-Item -ItemType Directory -Force -Path $script:logRoot | Out-Null
        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        Add-Content -Path $logFile -Value "[$timestamp] $ErrorText"
    } catch {
    }

    return $logFile
}

function Report-LauncherError {
    param(
        [string]$ErrorText,
        [string]$Title = "MONARC Local"
    )

    $logFile = Write-LauncherErrorLog $ErrorText
    Write-LauncherLog ("ERROR: {0}" -f $ErrorText)

    try {
        Add-Type -AssemblyName System.Windows.Forms
        [System.Windows.Forms.MessageBox]::Show(
            "$ErrorText`r`n`r`nDetails were written to:`r`n$logFile",
            $Title,
            [System.Windows.Forms.MessageBoxButtons]::OK,
            [System.Windows.Forms.MessageBoxIcon]::Error
        ) | Out-Null
    } catch {
        Write-Error $ErrorText
    }

    if ($script:form -and -not $script:form.IsDisposed) {
        try {
            $script:currentProcess = $null
            $script:currentAction = $null
            Set-Busy $false
            Set-StatusText "Launcher error" "The last action failed. See launcher-error.log for details."
            if ($script:outputBox) {
                Append-Output ("[ERROR] {0}{1}" -f $ErrorText, [Environment]::NewLine)
            }
        } catch {
        }
    }
}

trap {
    $errorText = ($_ | Out-String).Trim()
    Write-LauncherLog ("TRAP: {0}" -f $errorText)
    Report-LauncherError -ErrorText $errorText -Title "MONARC Local"
    if ($script:form -and -not $script:form.IsDisposed) {
        continue
    }
    exit 1
}

Write-LauncherLog "Launcher script starting."

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$binaryPath = Join-Path $scriptDir "monarc-local.exe"
$composeDir = Join-Path $scriptDir "compose"
$logoPath = Join-Path $composeDir "logo-monarc.png"

if (-not (Test-Path $binaryPath)) {
    Write-LauncherLog ("Launcher binary missing at {0}" -f $binaryPath)
    Write-Error "monarc-local.exe was not found next to the launcher."
    exit 1
}

$monarcHome = if ($env:MONARC_HOME_DIR) { $env:MONARC_HOME_DIR } else { Join-Path $env:LOCALAPPDATA "MonarcLocal" }
$userComposeDir = Join-Path $monarcHome "compose"
$userEnvFile = Join-Path $userComposeDir ".env"

New-Item -ItemType Directory -Force -Path $userComposeDir | Out-Null
if (-not (Test-Path $userEnvFile)) {
    $envExample = Join-Path $composeDir ".env.example"
    if (Test-Path $envExample) {
        Copy-Item $envExample $userEnvFile
        Write-LauncherLog ("Copied env example to {0}" -f $userEnvFile)
    }
}

if (-not $env:MONARC_COMPOSE_FILE) {
    $env:MONARC_COMPOSE_FILE = Join-Path $composeDir "docker-compose.release.yml"
}
if (-not $env:MONARC_ENV_FILE) {
    $env:MONARC_ENV_FILE = $userEnvFile
}

Write-LauncherLog ("Using compose file: {0}" -f $env:MONARC_COMPOSE_FILE)
Write-LauncherLog ("Using env file: {0}" -f $env:MONARC_ENV_FILE)

$CliArgs = @($CliArgs)
if ($CliArgs.Length -gt 0 -and -not [string]::IsNullOrWhiteSpace([string]$CliArgs[0])) {
    Write-LauncherLog ("CLI passthrough args: {0}" -f ([string]::Join(" ", $CliArgs)))
    & $binaryPath @CliArgs
    exit $LASTEXITCODE
}

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::SetUnhandledExceptionMode([System.Windows.Forms.UnhandledExceptionMode]::CatchException)
[System.Windows.Forms.Application]::add_ThreadException({
    param($sender, $eventArgs)
    $errorText = if ($eventArgs -and $eventArgs.Exception) { $eventArgs.Exception.ToString() } else { "Unknown WinForms thread exception." }
    Write-LauncherLog ("THREAD EXCEPTION: {0}" -f $errorText)
    Report-LauncherError -ErrorText $errorText -Title "MONARC Local"
})
[System.AppDomain]::CurrentDomain.add_UnhandledException({
    param($sender, $eventArgs)
    $exceptionObject = if ($eventArgs) { $eventArgs.ExceptionObject } else { $null }
    $errorText = if ($exceptionObject) { $exceptionObject.ToString() } else { "Unknown AppDomain exception." }
    Write-LauncherLog ("UNHANDLED EXCEPTION: {0}" -f $errorText)
    Report-LauncherError -ErrorText $errorText -Title "MONARC Local"
})
$script:pollTimer = New-Object System.Windows.Forms.Timer
$script:pollTimer.Interval = 250
$script:pollTimer.Add_Tick({ Poll-CurrentAction })

$actions = @(
    @{ Key = "start"; Title = "Start MONARC"; Detail = "Starting stack"; Args = @("up") },
    @{ Key = "update"; Title = "Update MONARC"; Detail = "Updating stack"; Args = @("update", "--migrate-cmd", "./scripts/update-all.sh") },
    @{ Key = "open"; Title = "Open In Browser"; Detail = "Opening browser"; Args = @("open") },
    @{ Key = "status"; Title = "Display Status"; Detail = "Inspecting status"; Args = @("status") },
    @{ Key = "logs"; Title = "Display Logs"; Detail = "Loading logs"; Args = @("logs", "--service", "monarc-frontoffice", "--tail", "200") },
    @{ Key = "config"; Title = "Edit Config"; Detail = "Opening config"; Args = @() },
    @{ Key = "help"; Title = "Help"; Detail = "Showing help"; Args = @("help") },
    @{ Key = "stop"; Title = "Stop MONARC"; Detail = "Stopping stack"; Args = @("down") }
)

$script:actionButtons = @{}
$script:actionMenuItems = @()
$script:currentProcess = $null
$script:currentAction = $null
$script:currentOutputFile = $null
$script:currentOutputPosition = 0

function New-RunnerEnvironment {
    $envTable = @{}
    Get-ChildItem Env: | ForEach-Object {
        $envTable[$_.Name] = $_.Value
    }
    $envTable["MONARC_COMPOSE_FILE"] = $env:MONARC_COMPOSE_FILE
    $envTable["MONARC_ENV_FILE"] = $env:MONARC_ENV_FILE
    return $envTable
}

function Append-Output {
    param([string]$Text)

    if ([string]::IsNullOrEmpty($Text)) {
        return
    }

    $appendAction = [System.Action[string]]{
        param($Chunk)
        $script:outputBox.AppendText($Chunk)
        $script:outputBox.SelectionStart = $script:outputBox.TextLength
        $script:outputBox.ScrollToCaret()
    }

    if ($script:outputBox.InvokeRequired) {
        $null = $script:outputBox.BeginInvoke($appendAction, @($Text))
    } else {
        $appendAction.Invoke($Text)
    }
}

function Set-StatusText {
    param(
        [string]$ActionText,
        [string]$DetailText
    )

    $updateAction = [System.Action]{
        $script:actionLabel.Text = $ActionText
        $script:detailLabel.Text = $DetailText
    }

    if ($script:form.InvokeRequired) {
        $null = $script:form.BeginInvoke($updateAction)
    } else {
        $updateAction.Invoke()
    }
}

function Set-Busy {
    param([bool]$Busy)

    $updateAction = [System.Action]{
        $script:progressBar.Visible = $Busy
        foreach ($button in $script:actionButtons.Values) {
            $button.Enabled = -not $Busy
        }
        foreach ($item in $script:actionMenuItems) {
            $item.Enabled = -not $Busy
        }
    }

    if ($script:form.InvokeRequired) {
        $null = $script:form.BeginInvoke($updateAction)
    } else {
        $updateAction.Invoke()
    }
}

function Show-Window {
    $showAction = [System.Action]{
        $script:form.WindowState = [System.Windows.Forms.FormWindowState]::Normal
        $script:form.Show()
        $script:form.BringToFront()
        $script:form.Activate()
    }

    if ($script:form.InvokeRequired) {
        $null = $script:form.BeginInvoke($showAction)
    } else {
        $showAction.Invoke()
    }
}

function Render-IdleState {
    Set-Busy $false
    Set-StatusText "Use the window buttons or MONARC tray menu" "The window stays centered and streams progress for the selected action."
    $script:outputBox.Text = @"
MONARC is ready.

Available actions:
- Start MONARC
- Update MONARC
- Open In Browser
- Display Status
- Display Logs
- Edit Config
- Help
- Stop MONARC

Default login: admin@admin.localhost
Default password: admin

Config file:
$userEnvFile
"@
}

function Read-CurrentProcessOutput {
    if ([string]::IsNullOrWhiteSpace($script:currentOutputFile) -or -not (Test-Path $script:currentOutputFile)) {
        return
    }

    $stream = $null
    $reader = $null
    try {
        $stream = New-Object System.IO.FileStream(
            $script:currentOutputFile,
            [System.IO.FileMode]::Open,
            [System.IO.FileAccess]::Read,
            [System.IO.FileShare]::ReadWrite
        )

        if ($stream.Length -le $script:currentOutputPosition) {
            return
        }

        $null = $stream.Seek($script:currentOutputPosition, [System.IO.SeekOrigin]::Begin)
        $reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::UTF8, $true, 4096, $true)
        $chunk = $reader.ReadToEnd()
        $script:currentOutputPosition = $stream.Position
        if (-not [string]::IsNullOrEmpty($chunk)) {
            Append-Output $chunk
        }
    } finally {
        if ($reader) {
            $reader.Dispose()
        }
        if ($stream) {
            $stream.Dispose()
        }
    }
}

function Complete-CurrentAction {
    if ($script:currentProcess -eq $null) {
        return
    }

    Read-CurrentProcessOutput

    $exitCode = 0
    $completedAction = $script:currentAction
    if ($script:currentProcess -is [System.Management.Automation.Job]) {
        $jobResults = @()
        try {
            $jobResults = @(Receive-Job -Job $script:currentProcess -Keep -ErrorAction Stop)
        } catch {
            $jobResults = @(
                [PSCustomObject]@{
                    ExitCode = 1
                    ErrorText = ($_ | Out-String).Trim()
                }
            )
        }

        foreach ($jobResult in $jobResults) {
            if ($null -eq $jobResult) {
                continue
            }

            $properties = $jobResult.PSObject.Properties
            if ($properties["ExitCode"]) {
                $exitCode = [int]$jobResult.ExitCode
            }
            if ($properties["ErrorText"] -and -not [string]::IsNullOrWhiteSpace([string]$jobResult.ErrorText)) {
                Append-Output ("[ERROR] {0}{1}" -f $jobResult.ErrorText, [Environment]::NewLine)
            }
        }

        Remove-Job -Job $script:currentProcess -Force -ErrorAction SilentlyContinue
    } else {
        $exitCode = $script:currentProcess.ExitCode
        $script:currentProcess.Dispose()
    }

    Write-LauncherLog ("Action completed with exit code {0} for action {1}" -f $exitCode, $(if ($completedAction) { $completedAction.Key } else { "<null>" }))

    $script:currentProcess = $null
    $script:currentAction = $null
    $script:currentOutputFile = $null
    $script:currentOutputPosition = 0

    if ($script:pollTimer) {
        $script:pollTimer.Stop()
    }

    Set-Busy $false
    if ($completedAction) {
        switch ($completedAction.Key) {
            "open" { Set-StatusText $completedAction.Detail "Browser opened." }
            "start" { Set-StatusText $completedAction.Detail "MONARC started." }
            "update" { Set-StatusText $completedAction.Detail "MONARC updated." }
            "stop" { Set-StatusText $completedAction.Detail "MONARC stopped." }
            default { Set-StatusText $completedAction.Detail "Completed." }
        }
        if ($exitCode -ne 0) {
            Set-StatusText $completedAction.Detail "Action failed."
            [System.Media.SystemSounds]::Exclamation.Play()
        }
    } else {
        Set-StatusText "MONARC action" "Completed."
    }
}

function Poll-CurrentAction {
    try {
        if ($script:currentProcess -eq $null) {
            if ($script:pollTimer) {
                $script:pollTimer.Stop()
            }
            return
        }

        Read-CurrentProcessOutput
        
        $isComplete = $false
        if ($script:currentProcess -is [System.Management.Automation.Job]) {
            $isComplete = $script:currentProcess.State -ne "Running"
        } else {
            $isComplete = $script:currentProcess.HasExited
        }

        if ($isComplete) {
            Complete-CurrentAction
        }
    } catch {
        Report-LauncherError -ErrorText (($_ | Out-String).Trim()) -Title "MONARC Local"
    }
}

function Start-MonarcAction {
    param($Action)

    try {
        $actionKey = "<null>"
        if ($null -ne $Action -and $Action.ContainsKey("Key")) {
            $actionKey = [string]$Action.Key
        }
        Write-LauncherLog ("Action requested: {0}" -f $actionKey)
        if ($script:currentProcess -ne $null) {
            Write-LauncherLog "Rejected action because another process is already running."
            [System.Windows.Forms.MessageBox]::Show("Another MONARC action is already running.", "MONARC Local") | Out-Null
            return
        }

        Show-Window
        Set-StatusText $Action.Detail ("Running {0}..." -f $Action.Title.ToLowerInvariant())
        $script:outputBox.Clear()
        Append-Output ("[MONARC] {0}{1}" -f $Action.Title, [Environment]::NewLine)

        if ($Action.Key -eq "config") {
            Write-LauncherLog ("Opening config file: {0}" -f $userEnvFile)
            Start-Process $userEnvFile | Out-Null
            Set-StatusText $Action.Detail ("Opened {0}." -f [System.IO.Path]::GetFileName($userEnvFile))
            Append-Output ("Opened config: {0}{1}" -f $userEnvFile, [Environment]::NewLine)
            return
        }

        $outputFile = Join-Path $script:logRoot ("action-{0}.log" -f ([Guid]::NewGuid().ToString("N")))

        $runnerEnv = New-RunnerEnvironment
        $asyncScriptBlock = {
            param($BinaryPath, [string[]]$CommandArgs, $OutputFile, $LogFile, $RunnerEnv)
            try {
                foreach ($entry in $RunnerEnv.GetEnumerator()) {
                    Set-Item -Path ("Env:{0}" -f $entry.Key) -Value ([string]$entry.Value)
                }

                if (Test-Path $OutputFile) {
                    Remove-Item -Path $OutputFile -Force -ErrorAction SilentlyContinue
                }

                & $BinaryPath @CommandArgs 2>&1 | ForEach-Object {
                    $_ | Out-String
                } | Out-File -FilePath $OutputFile -Encoding UTF8 -Append

                $exitCode = if ($null -ne $LASTEXITCODE) { [int]$LASTEXITCODE } else { 0 }
                Add-Content -Path $LogFile -Value ("Async job completed with exit code {0}" -f $exitCode)
                [PSCustomObject]@{
                    ExitCode = $exitCode
                    ErrorText = $null
                }
            } catch {
                $errorText = ($_ | Out-String).Trim()
                Add-Content -Path $LogFile -Value ("Async script error: {0}" -f $errorText)
                [PSCustomObject]@{
                    ExitCode = 1
                    ErrorText = $errorText
                }
            }
        }

        Write-LauncherLog ("Starting async command: {0} {1}" -f $binaryPath, ([string]::Join(" ", $Action.Args)))

        $job = Start-Job -ScriptBlock $asyncScriptBlock -ArgumentList $binaryPath, $Action.Args, $outputFile, $script:launcherLogFile, $runnerEnv

        $script:currentAction = $Action
        $script:currentProcess = $job
        $script:currentOutputFile = $outputFile
        $script:currentOutputPosition = 0
        Set-Busy $true

        Write-LauncherLog ("Async job started successfully. Job ID={0}" -f $job.Id)
        if ($script:pollTimer) {
            $script:pollTimer.Start()
        }
    } catch {
        Report-LauncherError -ErrorText (($_ | Out-String).Trim()) -Title "MONARC Local"
    }
}

$form = New-Object System.Windows.Forms.Form
$script:form = $form
$form.Text = "MONARC Local"
$form.StartPosition = "CenterScreen"
$form.ClientSize = New-Object System.Drawing.Size(760, 520)
$form.BackColor = [System.Drawing.Color]::FromArgb(250, 245, 235)
$form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
$form.MaximizeBox = $false

$logoBox = New-Object System.Windows.Forms.PictureBox
$logoBox.Bounds = New-Object System.Drawing.Rectangle(235, 18, 290, 88)
$logoBox.SizeMode = [System.Windows.Forms.PictureBoxSizeMode]::Zoom
if (Test-Path $logoPath) {
    $logoBox.Image = [System.Drawing.Image]::FromFile($logoPath)
}
$form.Controls.Add($logoBox)

$actionLabel = New-Object System.Windows.Forms.Label
$script:actionLabel = $actionLabel
$actionLabel.Bounds = New-Object System.Drawing.Rectangle(40, 112, 680, 24)
$actionLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$actionLabel.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 15)
$actionLabel.ForeColor = [System.Drawing.Color]::FromArgb(86, 64, 28)
$form.Controls.Add($actionLabel)

$detailLabel = New-Object System.Windows.Forms.Label
$script:detailLabel = $detailLabel
$detailLabel.Bounds = New-Object System.Drawing.Rectangle(80, 140, 600, 20)
$detailLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$detailLabel.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$detailLabel.ForeColor = [System.Drawing.Color]::FromArgb(120, 102, 72)
$form.Controls.Add($detailLabel)

$buttonSize = New-Object System.Drawing.Size(150, 34)
$buttonPoints = @(
    (New-Object System.Drawing.Point(60, 182)),
    (New-Object System.Drawing.Point(222, 182)),
    (New-Object System.Drawing.Point(384, 182)),
    (New-Object System.Drawing.Point(546, 182)),
    (New-Object System.Drawing.Point(60, 226)),
    (New-Object System.Drawing.Point(222, 226)),
    (New-Object System.Drawing.Point(384, 226)),
    (New-Object System.Drawing.Point(546, 226))
)

for ($index = 0; $index -lt $actions.Count; $index++) {
    $action = $actions[$index]
    $button = New-Object System.Windows.Forms.Button
    $button.Size = $buttonSize
    $button.Location = $buttonPoints[$index]
    $button.Text = $action.Title
    $button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
    $button.FlatAppearance.BorderSize = 0
    $button.ForeColor = [System.Drawing.Color]::White
    $button.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 9)
    switch ($action.Key) {
        "start" { $button.BackColor = [System.Drawing.Color]::FromArgb(83, 61, 25) }
        "update" { $button.BackColor = [System.Drawing.Color]::FromArgb(83, 61, 25) }
        "open" { $button.BackColor = [System.Drawing.Color]::FromArgb(83, 61, 25) }
        "stop" { $button.BackColor = [System.Drawing.Color]::FromArgb(120, 40, 28) }
        default { $button.BackColor = [System.Drawing.Color]::FromArgb(132, 105, 58) }
    }
    $button.Tag = $action
    $button.Add_Click({
        param($sender, $eventArgs)
        Start-MonarcAction $sender.Tag
    })
    $form.Controls.Add($button)
    $script:actionButtons[$action.Key] = $button
}

$progressBar = New-Object System.Windows.Forms.ProgressBar
$script:progressBar = $progressBar
$progressBar.Bounds = New-Object System.Drawing.Rectangle(280, 296, 200, 8)
$progressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
$progressBar.Visible = $false
$form.Controls.Add($progressBar)

$credentialsLabel = New-Object System.Windows.Forms.Label
$credentialsLabel.Bounds = New-Object System.Drawing.Rectangle(40, 268, 680, 22)
$credentialsLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$credentialsLabel.Font = New-Object System.Drawing.Font("Segoe UI Semibold", 9)
$credentialsLabel.ForeColor = [System.Drawing.Color]::FromArgb(86, 64, 28)
$credentialsLabel.Text = "Default login: admin@admin.localhost    Default password: admin"
$form.Controls.Add($credentialsLabel)

$outputBox = New-Object System.Windows.Forms.TextBox
$script:outputBox = $outputBox
$outputBox.Multiline = $true
$outputBox.ReadOnly = $true
$outputBox.ScrollBars = "Vertical"
$outputBox.WordWrap = $false
$outputBox.Bounds = New-Object System.Drawing.Rectangle(38, 322, 684, 160)
$outputBox.BackColor = [System.Drawing.Color]::FromArgb(33, 31, 27)
$outputBox.ForeColor = [System.Drawing.Color]::FromArgb(238, 227, 208)
$outputBox.Font = New-Object System.Drawing.Font("Consolas", 10)
$form.Controls.Add($outputBox)

$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.Text = "MONARC Local"
$notifyIcon.Icon = [System.Drawing.SystemIcons]::Application
$notifyIcon.Visible = $true
$notifyIcon.Add_DoubleClick({ Show-Window })

$menu = New-Object System.Windows.Forms.ContextMenuStrip
foreach ($action in $actions) {
    $item = $menu.Items.Add($action.Title)
    $item.Tag = $action
    $item.Add_Click({
        param($sender, $eventArgs)
        Start-MonarcAction $sender.Tag
    })
    $script:actionMenuItems += $item
}
$menu.Items.Add("-") | Out-Null
$showItem = $menu.Items.Add("Show MONARC Window")
$showItem.Add_Click({ Show-Window })
$quitItem = $menu.Items.Add("Quit")
$quitItem.Add_Click({
    $notifyIcon.Visible = $false
    $form.Close()
})
$notifyIcon.ContextMenuStrip = $menu

$form.Add_FormClosing({
    $notifyIcon.Visible = $false
})

Render-IdleState
[System.Windows.Forms.Application]::EnableVisualStyles()
[void][System.Windows.Forms.Application]::Run($form)
