param(
    [string]$Root = ".\\app",
    [int]$Port = 8765
)

$ErrorActionPreference = "Stop"

function Get-ContentType([string]$Path) {
    switch ([System.IO.Path]::GetExtension($Path).ToLowerInvariant()) {
        ".html" { "text/html; charset=utf-8" }
        ".js"   { "application/javascript; charset=utf-8" }
        ".css"  { "text/css; charset=utf-8" }
        ".json" { "application/json; charset=utf-8" }
        ".png"  { "image/png" }
        ".jpg"  { "image/jpeg" }
        ".jpeg" { "image/jpeg" }
        ".svg"  { "image/svg+xml" }
        ".txt"  { "text/plain; charset=utf-8" }
        default { "application/octet-stream" }
    }
}

$rootPath = [System.IO.Path]::GetFullPath($Root)
$prefix = "http://127.0.0.1:$Port/"
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse("127.0.0.1"), $Port)
$listener.Start()

Write-Host "DPC-CLIP local server started: $prefix"
Start-Process $prefix

try {
    while ($true) {
        $client = $listener.AcceptTcpClient()
        $stream = $client.GetStream()
        $reader = New-Object System.IO.StreamReader($stream, [System.Text.Encoding]::ASCII, $false, 8192, $true)
        $requestLine = $reader.ReadLine()
        if ([string]::IsNullOrWhiteSpace($requestLine)) {
            $client.Close()
            continue
        }

        while ($true) {
            $headerLine = $reader.ReadLine()
            if ([string]::IsNullOrEmpty($headerLine)) {
                break
            }
        }

        $parts = $requestLine.Split(" ")
        $method = $parts[0]
        $rawTarget = $parts[1]
        $pathPart = $rawTarget.Split("?")[0]
        $decodedPath = [System.Uri]::UnescapeDataString($pathPart.TrimStart("/"))
        if ([string]::IsNullOrWhiteSpace($decodedPath)) {
            $decodedPath = "index.html"
        }

        $fullPath = [System.IO.Path]::GetFullPath((Join-Path $rootPath $decodedPath))
        if (-not $fullPath.StartsWith($rootPath)) {
            $responseBody = [System.Text.Encoding]::UTF8.GetBytes("Forbidden")
            $header = "HTTP/1.1 403 Forbidden`r`nContent-Type: text/plain; charset=utf-8`r`nContent-Length: $($responseBody.Length)`r`nConnection: close`r`n`r`n"
            $stream.Write([System.Text.Encoding]::ASCII.GetBytes($header), 0, $header.Length)
            $stream.Write($responseBody, 0, $responseBody.Length)
            $client.Close()
            continue
        }

        if ((Test-Path $fullPath) -and (Get-Item $fullPath).PSIsContainer) {
            $fullPath = Join-Path $fullPath "index.html"
        }

        if ($method -ne "GET" -or -not (Test-Path $fullPath)) {
            $responseBody = [System.Text.Encoding]::UTF8.GetBytes("Not Found")
            $header = "HTTP/1.1 404 Not Found`r`nContent-Type: text/plain; charset=utf-8`r`nContent-Length: $($responseBody.Length)`r`nConnection: close`r`n`r`n"
            $stream.Write([System.Text.Encoding]::ASCII.GetBytes($header), 0, $header.Length)
            $stream.Write($responseBody, 0, $responseBody.Length)
            $client.Close()
            continue
        }

        $bytes = [System.IO.File]::ReadAllBytes($fullPath)
        $contentType = Get-ContentType $fullPath
        $header = "HTTP/1.1 200 OK`r`nContent-Type: $contentType`r`nContent-Length: $($bytes.Length)`r`nCache-Control: no-store`r`nConnection: close`r`n`r`n"
        $headerBytes = [System.Text.Encoding]::ASCII.GetBytes($header)
        $stream.Write($headerBytes, 0, $headerBytes.Length)
        $stream.Write($bytes, 0, $bytes.Length)
        $client.Close()
    }
}
finally {
    $listener.Stop()
}
