# Ikonka Bazy Polis przy zegarku (obszar powiadomień Windows).
# Uruchamia aktualizator i serwer Bazy Polis bez żadnego okna terminala,
# a przy zegarku pokazuje niebieską ikonkę "BP" z menu:
#   Otwórz Bazę Polis / Uruchom ponownie / Zamknij Bazę Polis.
# Odpalane przez "Uruchom Baza Polis w tle.vbs" z katalogu głównego aplikacji.
# Komunikaty dla użytkownika są proste; szczegóły techniczne trafiają do
# pliki\tmp\serwer-log.txt, serwer-bledy.txt, aktualizator-log.txt, aktualizator-bledy.txt.

$ErrorActionPreference = 'Stop'

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$toolsDirectory = $PSScriptRoot
$plikiDirectory = Split-Path -Parent $toolsDirectory
$stopScriptPath = Join-Path $toolsDirectory 'zatrzymaj-baza-polis.ps1'
$logDirectory = Join-Path $plikiDirectory 'tmp'
$applicationIconPath = Join-Path $plikiDirectory 'assets\iseca-icon.ico'

$applicationPort = 3210
if ($env:BAZA_POLIS_PORT -match '^\d+$') { $applicationPort = [int]$env:BAZA_POLIS_PORT }
$applicationUrl = "http://127.0.0.1:$applicationPort/start.htm"

$updaterFailedMessage = @"
Aktualizacja nie została zainstalowana, więc Iseca nie została uruchomiona (aby nie uszkodzić plików).

Spróbuj ponownie: kliknij ikonkę Iseca przy zegarku. Jeśli problem wróci, uruchom plik „Uruchom Baza Polis.cmd", aby zobaczyć szczegóły.
"@

$startFailedMessage = @"
Nie udało się uruchomić Iseca.

Spróbuj ponownie: kliknij ikonkę Iseca przy zegarku. Jeśli problem wróci, uruchom plik „Uruchom Baza Polis.cmd", aby zobaczyć szczegóły.
"@

# Jedna ikonka naraz: kolejne uruchomienie tylko otwiera aplikację w przeglądarce.
$mutexCreated = $false
$singleInstanceMutex = [System.Threading.Mutex]::new($true, 'SekagentTray', [ref]$mutexCreated)
if (-not $mutexCreated) {
  if (Get-NetTCPConnection -LocalPort $applicationPort -State Listen -ErrorAction SilentlyContinue) {
    Start-Process $applicationUrl
  }
  exit
}

function Test-ServerListening {
  return [bool](Get-NetTCPConnection -LocalPort $applicationPort -State Listen -ErrorAction SilentlyContinue)
}

function New-SekagentIcon {
  if (-not (Test-Path -LiteralPath $applicationIconPath)) {
    throw "Brak pliku ikony Iseca: $applicationIconPath"
  }
  return [System.Drawing.Icon]::new($applicationIconPath)
}

# Uruchamia node bez okna; wyjście trafia do plików w pliki\tmp.
# Paczka pracownika ma własny node.exe w pliki\node - wtedy nie wymaga instalacji Node.
function Start-HiddenNode([string]$scriptFileName, [string]$logBaseName) {
  New-Item -ItemType Directory -Force -Path $logDirectory | Out-Null
  $nodeExecutable = 'node'
  $localNodeExecutable = Join-Path $plikiDirectory 'node\node.exe'
  if (Test-Path -LiteralPath $localNodeExecutable) { $nodeExecutable = $localNodeExecutable }
  $process = Start-Process -FilePath $nodeExecutable `
    -ArgumentList ('"{0}"' -f $scriptFileName) `
    -WorkingDirectory $plikiDirectory `
    -WindowStyle Hidden -PassThru `
    -RedirectStandardOutput (Join-Path $logDirectory ($logBaseName + '-log.txt')) `
    -RedirectStandardError (Join-Path $logDirectory ($logBaseName + '-bledy.txt'))
  # Bez pobrania uchwytu PowerShell 5.1 gubi ExitCode po zakończeniu procesu.
  $null = $process.Handle
  return $process
}

$script:applicationState = ''
$script:startupPhase = ''
$script:updaterProcess = $null
$script:serverProcess = $null
$script:stopProcess = $null
$script:restartAfterStop = $false
$script:portMissCount = 0

function Set-ApplicationState([string]$newState) {
  $script:applicationState = $newState
  $script:portMissCount = 0
  switch ($newState) {
    'starting' {
      $statusMenuItem.Text = 'Uruchamianie…'
      $notifyIcon.Text = 'Iseca - uruchamianie…'
      $notifyIcon.Icon = $runningIcon
    }
    'running' {
      $statusMenuItem.Text = 'Działa'
      $notifyIcon.Text = 'Iseca - działa (kliknij, aby otworzyć)'
      $notifyIcon.Icon = $runningIcon
    }
    'stopping' {
      $statusMenuItem.Text = 'Zatrzymywanie…'
      $notifyIcon.Text = 'Iseca - zatrzymywanie…'
      $notifyIcon.Icon = $stoppedIcon
    }
    'stopped' {
      $statusMenuItem.Text = 'Zatrzymana'
      $notifyIcon.Text = 'Iseca - zatrzymana (kliknij, aby włączyć)'
      $notifyIcon.Icon = $stoppedIcon
    }
  }
  $restartMenuItem.Enabled = ($newState -eq 'running' -or $newState -eq 'stopped')
  $closeMenuItem.Enabled = ($newState -ne 'stopping')
}

function Start-ApplicationSequence {
  if (Test-ServerListening) {
    # Serwer już działa (np. uruchomiony wcześniej oknem) - tylko przejmujemy nadzór.
    $script:serverProcess = $null
    Set-ApplicationState 'running'
    $notifyIcon.ShowBalloonTip(4000, 'Iseca', 'Iseca już działa. Ikonka jest przy zegarku.', [System.Windows.Forms.ToolTipIcon]::Info)
    return
  }
  Set-ApplicationState 'starting'
  $script:startupPhase = 'updater'
  try {
    $script:updaterProcess = Start-HiddenNode 'aktualizator.mjs' 'aktualizator'
  } catch {
    Set-ApplicationState 'stopped'
    [void][System.Windows.Forms.MessageBox]::Show($startFailedMessage, 'Iseca', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
  }
}

function Request-Stop([bool]$restartAfter) {
  if ($script:applicationState -eq 'stopping') { return }
  $script:restartAfterStop = $restartAfter
  Set-ApplicationState 'stopping'
  try {
    $script:stopProcess = Start-Process -FilePath 'powershell.exe' `
      -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', ('"{0}"' -f $stopScriptPath)) `
      -WindowStyle Hidden -PassThru
    $null = $script:stopProcess.Handle
  } catch {
    $script:stopProcess = $null
  }
}

function Open-Application {
  if ($script:applicationState -eq 'running') {
    Start-Process $applicationUrl
    return
  }
  if ($script:applicationState -eq 'stopped') { Start-ApplicationSequence }
}

function Invoke-ApplicationExit {
  $stateTimer.Stop()
  $notifyIcon.Visible = $false
  $notifyIcon.Dispose()
  [System.Windows.Forms.Application]::Exit()
}

function Invoke-StateTick {
  try {
    switch ($script:applicationState) {
      'starting' {
        if ($script:startupPhase -eq 'updater') {
          if (-not $script:updaterProcess -or $script:updaterProcess.HasExited) {
            $updaterExitCode = 1
            if ($script:updaterProcess) { $updaterExitCode = $script:updaterProcess.ExitCode }
            if ($updaterExitCode -ne 0) {
              Set-ApplicationState 'stopped'
              [void][System.Windows.Forms.MessageBox]::Show($updaterFailedMessage, 'Iseca', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
              return
            }
            $script:startupPhase = 'server'
            try {
              $script:serverProcess = Start-HiddenNode 'serwer.mjs' 'serwer'
            } catch {
              Set-ApplicationState 'stopped'
              [void][System.Windows.Forms.MessageBox]::Show($startFailedMessage, 'Iseca', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
            }
          }
          return
        }
        if (Test-ServerListening) {
          Set-ApplicationState 'running'
          $notifyIcon.ShowBalloonTip(4000, 'Iseca', 'Uruchomiona', [System.Windows.Forms.ToolTipIcon]::Info)
          return
        }
        if ($script:serverProcess -and $script:serverProcess.HasExited) {
          if (Test-ServerListening) {
            # Wyścig o port: inny serwer już nasłuchuje - przejmujemy nadzór.
            $script:serverProcess = $null
            Set-ApplicationState 'running'
            return
          }
          Set-ApplicationState 'stopped'
          [void][System.Windows.Forms.MessageBox]::Show($startFailedMessage, 'Iseca', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning)
        }
        return
      }
      'running' {
        if ($script:serverProcess) {
          if (-not $script:serverProcess.HasExited) { return }
          if (Test-ServerListening) { $script:serverProcess = $null; return }
        } else {
          # Nadzór po porcie (serwer uruchomiony poza ikonką) - z zapasem na chwilowe pudła.
          if (Test-ServerListening) { $script:portMissCount = 0; return }
          $script:portMissCount++
          if ($script:portMissCount -lt 3) { return }
        }
        Set-ApplicationState 'stopped'
        $notifyIcon.ShowBalloonTip(6000, 'Iseca', 'Iseca została zatrzymana. Kliknij ikonkę, aby włączyć ją ponownie.', [System.Windows.Forms.ToolTipIcon]::Warning)
        return
      }
      'stopping' {
        if ($script:stopProcess -and -not $script:stopProcess.HasExited) { return }
        $script:stopProcess = $null
        if ($script:restartAfterStop) {
          $script:restartAfterStop = $false
          Start-ApplicationSequence
        } else {
          Invoke-ApplicationExit
        }
        return
      }
    }
  } catch {
    # Pojedynczy błąd pętli stanu nie może zamknąć ikonki.
  }
}

$runningIcon = New-SekagentIcon
$stoppedIcon = New-SekagentIcon

$contextMenu = [System.Windows.Forms.ContextMenuStrip]::new()
$statusMenuItem = [System.Windows.Forms.ToolStripMenuItem]::new('…')
$statusMenuItem.Enabled = $false
$openMenuItem = [System.Windows.Forms.ToolStripMenuItem]::new('Otwórz Iseca')
$openMenuItem.Font = [System.Drawing.Font]::new($openMenuItem.Font, [System.Drawing.FontStyle]::Bold)
$restartMenuItem = [System.Windows.Forms.ToolStripMenuItem]::new('Uruchom ponownie')
$closeMenuItem = [System.Windows.Forms.ToolStripMenuItem]::new('Zamknij Iseca')
[void]$contextMenu.Items.Add($statusMenuItem)
[void]$contextMenu.Items.Add([System.Windows.Forms.ToolStripSeparator]::new())
[void]$contextMenu.Items.Add($openMenuItem)
[void]$contextMenu.Items.Add($restartMenuItem)
[void]$contextMenu.Items.Add([System.Windows.Forms.ToolStripSeparator]::new())
[void]$contextMenu.Items.Add($closeMenuItem)

$notifyIcon = [System.Windows.Forms.NotifyIcon]::new()
$notifyIcon.Icon = $runningIcon
$notifyIcon.Text = 'Iseca'
$notifyIcon.ContextMenuStrip = $contextMenu
$notifyIcon.Visible = $true

$openMenuItem.add_Click({ Open-Application })
$restartMenuItem.add_Click({ Request-Stop $true })
$closeMenuItem.add_Click({ Request-Stop $false })
$notifyIcon.add_MouseClick({
  param($sender, $eventArgs)
  if ($eventArgs.Button -eq [System.Windows.Forms.MouseButtons]::Left) { Open-Application }
})

$stateTimer = [System.Windows.Forms.Timer]::new()
$stateTimer.Interval = 800
$stateTimer.add_Tick({ Invoke-StateTick })

Start-ApplicationSequence
$stateTimer.Start()

try {
  [System.Windows.Forms.Application]::Run()
} finally {
  $stateTimer.Stop()
  $notifyIcon.Visible = $false
  $notifyIcon.Dispose()
  $runningIcon.Dispose()
  $stoppedIcon.Dispose()
  try { $singleInstanceMutex.ReleaseMutex() } catch {}
  $singleInstanceMutex.Dispose()
}
