param(
  [int]$Port = 8199,
  [string]$Edition = "Normal"
)

$ErrorActionPreference = "Stop"
$root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot "source\public"))

if (-not (Test-Path (Join-Path $root "index.html"))) {
  Write-Host "SiteSight could not find its app files." -ForegroundColor Red
  Write-Host "Keep OPEN-SITESIGHT.bat, Start-SiteSight.ps1 and the source folder together."
  exit 1
}

$mime = @{
  ".html" = "text/html; charset=utf-8"
  ".js" = "text/javascript; charset=utf-8"
  ".css" = "text/css; charset=utf-8"
  ".json" = "application/json; charset=utf-8"
  ".webmanifest" = "application/manifest+json; charset=utf-8"
  ".svg" = "image/svg+xml"
  ".png" = "image/png"
  ".jpg" = "image/jpeg"
  ".jpeg" = "image/jpeg"
  ".gif" = "image/gif"
  ".webp" = "image/webp"
  ".pdf" = "application/pdf"
  ".csv" = "text/csv; charset=utf-8"
  ".txt" = "text/plain; charset=utf-8"
  ".wasm" = "application/wasm"
  ".bcmap" = "application/octet-stream"
}

$listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, $Port)

try {
  $listener.Start()
} catch {
  Write-Host "SiteSight could not start because local port $Port is already in use." -ForegroundColor Red
  Write-Host "Close any other SiteSight test window and try again."
  exit 1
}

$url = "http://localhost:$Port/"
Write-Host ""
Write-Host "SiteSight V1.99.8 $Edition is running." -ForegroundColor Green
Write-Host "Your browser should open automatically."
Write-Host "Keep this window open while testing."
Write-Host "Close this window when you have finished."
Write-Host ""
Start-Process $url

try {
  while ($true) {
    $client = $listener.AcceptTcpClient()
    try {
      $stream = $client.GetStream()
      $reader = [IO.StreamReader]::new($stream, [Text.Encoding]::ASCII, $false, 4096, $true)
      $requestLine = $reader.ReadLine()
      if ([string]::IsNullOrWhiteSpace($requestLine)) { continue }
      while (($line = $reader.ReadLine()) -ne $null -and $line -ne "") { }

      $parts = $requestLine.Split(" ")
      $requestPath = if ($parts.Length -ge 2) { $parts[1].Split("?")[0] } else { "/" }
      $relative = [Uri]::UnescapeDataString($requestPath.TrimStart("/")).Replace("/", [IO.Path]::DirectorySeparatorChar)
      if ([string]::IsNullOrWhiteSpace($relative)) { $relative = "index.html" }

      $fullPath = [IO.Path]::GetFullPath((Join-Path $root $relative))
      $insideRoot = $fullPath.Equals($root, [StringComparison]::OrdinalIgnoreCase) -or $fullPath.StartsWith($root + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)
      if (-not $insideRoot) { throw "Blocked path" }
      if (Test-Path $fullPath -PathType Container) { $fullPath = Join-Path $fullPath "index.html" }

      if (Test-Path $fullPath -PathType Leaf) {
        $body = [IO.File]::ReadAllBytes($fullPath)
        $extension = [IO.Path]::GetExtension($fullPath).ToLowerInvariant()
        $contentType = if ($mime.ContainsKey($extension)) { $mime[$extension] } else { "application/octet-stream" }
        $status = "200 OK"
      } else {
        $body = [Text.Encoding]::UTF8.GetBytes("Not found")
        $contentType = "text/plain; charset=utf-8"
        $status = "404 Not Found"
      }

      $header = "HTTP/1.1 $status`r`nContent-Type: $contentType`r`nContent-Length: $($body.Length)`r`nCache-Control: no-store`r`nConnection: close`r`n`r`n"
      $headerBytes = [Text.Encoding]::ASCII.GetBytes($header)
      $stream.Write($headerBytes, 0, $headerBytes.Length)
      $stream.Write($body, 0, $body.Length)
      $stream.Flush()
    } catch {
      # Close the failed request and keep SiteSight available for the next one.
    } finally {
      $client.Close()
    }
  }
} finally {
  $listener.Stop()
}
