param(
    [string]$Server = "",

    [string]$TokenFile = "",

    [string]$OutputDir = "",

    [string]$HostDevice = "host-001",

    [string]$ViewerDevice = "viewer-001",

    [string]$HostDeviceTokenFile = "",

    [string]$ViewerDeviceTokenFile = "",

    [ValidateSet("disabled", "optional", "required")]
    [string]$TransportEncryption = "disabled",

    [string]$TransportSecret = "",

    [string]$TransportSecretFile = "",

    [switch]$IncludeDeviceTokenTemplates,

    [switch]$Force,

    [switch]$TestMode
)

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

function Assert-SyncVisionClientConfigTokenFile {
    param(
        [Parameter(Mandatory = $true)]
        [string]$TokenFile
    )

    if ([string]::IsNullOrWhiteSpace($TokenFile)) {
        throw "TokenFile is required."
    }
    if (-not (Test-Path -LiteralPath $TokenFile -PathType Leaf)) {
        throw "TokenFile was not found: $TokenFile"
    }
    $tokenText = [IO.File]::ReadAllText((Resolve-Path -LiteralPath $TokenFile).Path)
    if ([string]::IsNullOrWhiteSpace($tokenText)) {
        throw "TokenFile must not be empty: $TokenFile"
    }
}

function Resolve-SyncVisionClientConfigOutputPath {
    param(
        [Parameter(Mandatory = $true)]
        [string]$OutputDir,

        [Parameter(Mandatory = $true)]
        [string]$FileName
    )

    return (Join-Path $OutputDir $FileName)
}

function Resolve-SyncVisionClientConfigTransportSecret {
    param(
        [ValidateSet("disabled", "optional", "required")]
        [string]$TransportEncryption = "disabled",

        [AllowEmptyString()]
        [string]$TransportSecret = "",

        [AllowEmptyString()]
        [string]$TransportSecretFile = ""
    )

    if ($TransportEncryption -eq "disabled") {
        return ""
    }
    if (-not [string]::IsNullOrWhiteSpace($TransportSecret) -and
        -not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        throw "Pass either -TransportSecret or -TransportSecretFile, not both."
    }
    if (-not [string]::IsNullOrWhiteSpace($TransportSecretFile)) {
        if (-not (Test-Path -LiteralPath $TransportSecretFile -PathType Leaf)) {
            throw "TransportSecretFile was not found: $TransportSecretFile"
        }

        $secretFromFile = ([IO.File]::ReadAllText($TransportSecretFile)).Trim()
        if ([string]::IsNullOrWhiteSpace($secretFromFile)) {
            throw "TransportSecretFile must not be empty: $TransportSecretFile"
        }

        return $secretFromFile
    }
    if ([string]::IsNullOrWhiteSpace($TransportSecret)) {
        throw "TransportSecret or TransportSecretFile is required when TransportEncryption is optional or required."
    }

    return $TransportSecret
}

function New-SyncVisionClientPublicConfigContent {
    param(
        [string]$Server,

        [Parameter(Mandatory = $true)]
        [string]$TokenFileName,

        [string]$DeviceTokenFileName = "",

        [ValidateSet("disabled", "optional", "required")]
        [string]$TransportEncryption = "disabled",

        [AllowEmptyString()]
        [string]$TransportSecretFileName = "",

        [switch]$IncludeDeviceTokenTemplates
    )

    $lines = New-Object System.Collections.Generic.List[string]
    if (-not [string]::IsNullOrWhiteSpace($Server)) {
        $lines.Add("server=$Server")
    }
    $lines.Add("token_file=$TokenFileName")
    if (-not [string]::IsNullOrWhiteSpace($DeviceTokenFileName)) {
        $lines.Add("device_token_file=$DeviceTokenFileName")
    }
    elseif ($IncludeDeviceTokenTemplates) {
        $lines.Add("# Optional per-device hardening: device_token_file=<path-to-this-device-token>")
    }
    if ($TransportEncryption -ne "disabled") {
        if ([string]::IsNullOrWhiteSpace($TransportSecretFileName)) {
            throw "TransportSecretFileName cannot be empty when TransportEncryption is optional or required."
        }
        $lines.Add("transport_encryption=$TransportEncryption")
        $lines.Add("transport_secret_file=$TransportSecretFileName")
    }
    $lines.Add("reconnect_attempts=3")
    $lines.Add("reconnect_delay_ms=1000")
    $lines.Add("reconnect_max_delay_ms=5000")

    return (($lines -join [Environment]::NewLine) + [Environment]::NewLine)
}

function New-SyncVisionGeneratedStarterContent {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet("host", "viewer")]
        [string]$Mode,

        [Parameter(Mandatory = $true)]
        [string]$ClientConfigFileName,

        [Parameter(Mandatory = $true)]
        [string]$HostDevice,

        [Parameter(Mandatory = $true)]
        [string]$ViewerDevice,

        [string]$DeviceTokenFileName = "",

        [switch]$IncludeDeviceTokenTemplates
    )

    $wrapperName = if ($Mode -eq "host") { "start-host.ps1" } else { "start-viewer.ps1" }
    $device = if ($Mode -eq "host") { $HostDevice } else { $ViewerDevice }

    $commandLines = New-Object System.Collections.Generic.List[string]
    $commandLines.Add("& `$wrapperScript ``")
    $commandLines.Add("    -Configuration Release ``")
    $commandLines.Add("    -ClientConfig (Join-Path `$PSScriptRoot `"$ClientConfigFileName`") ``")
    $commandLines.Add("    -Device `"$device`" ``")
    if ($Mode -eq "host") {
        $commandLines.Add("    -AllowViewer `"$ViewerDevice`" ``")
        $commandLines.Add("    -GrantInputTo `"$ViewerDevice`" ``")
        if (-not [string]::IsNullOrWhiteSpace($DeviceTokenFileName)) {
            $commandLines.Add("    -DeviceTokenFile (Join-Path `$PSScriptRoot `"$DeviceTokenFileName`") ``")
        }
        $commandLines.Add("    -ReceiveDir (Join-Path `$PSScriptRoot `"received`")")
    }
    else {
        $commandLines.Add("    -Target `"$HostDevice`"")
        if (-not [string]::IsNullOrWhiteSpace($DeviceTokenFileName)) {
            $commandLines[$commandLines.Count - 1] = "    -Target `"$HostDevice`" ``"
            $commandLines.Add("    -DeviceTokenFile (Join-Path `$PSScriptRoot `"$DeviceTokenFileName`")")
        }
    }

    $templateLines = @()
    if ([string]::IsNullOrWhiteSpace($DeviceTokenFileName) -and $IncludeDeviceTokenTemplates) {
        $templateLines = @(
            "# Optional per-device hardening:",
            "# Add -DeviceTokenFile (Join-Path `$PSScriptRoot `"$Mode.device.token`") after creating that file."
        )
    }

    return @"
Set-StrictMode -Version Latest
`$ErrorActionPreference = "Stop"

`$wrapperCandidates = @(
    (Join-Path `$PSScriptRoot "..\scripts\$wrapperName"),
    (Join-Path `$PSScriptRoot "scripts\$wrapperName"),
    (Join-Path `$PSScriptRoot "$wrapperName")
)
`$wrapperScript = `$wrapperCandidates | Where-Object { Test-Path -LiteralPath `$_ -PathType Leaf } | Select-Object -First 1
if ([string]::IsNullOrWhiteSpace(`$wrapperScript)) {
    throw "Could not find $wrapperName. Run this from a release package client config directory, package root, or place it next to scripts."
}

$($templateLines -join [Environment]::NewLine)
$($commandLines -join [Environment]::NewLine)
"@
}

function New-SyncVisionClientConfigPlan {
    param(
        [string]$Server,

        [Parameter(Mandatory = $true)]
        [string]$TokenFile,

        [Parameter(Mandatory = $true)]
        [string]$OutputDir,

        [string]$HostDevice = "host-001",

        [string]$ViewerDevice = "viewer-001",

        [string]$HostDeviceTokenFile = "",

        [string]$ViewerDeviceTokenFile = "",

        [ValidateSet("disabled", "optional", "required")]
        [string]$TransportEncryption = "disabled",

        [AllowEmptyString()]
        [string]$TransportSecret = "",

        [AllowEmptyString()]
        [string]$TransportSecretFile = "",

        [switch]$IncludeDeviceTokenTemplates
    )

    if ([string]::IsNullOrWhiteSpace($OutputDir)) {
        throw "OutputDir is required."
    }
    if ([string]::IsNullOrWhiteSpace($HostDevice)) {
        throw "HostDevice is required."
    }
    if ([string]::IsNullOrWhiteSpace($ViewerDevice)) {
        throw "ViewerDevice is required."
    }
    Assert-SyncVisionClientConfigTokenFile -TokenFile $TokenFile

    $resolvedTokenFile = (Resolve-Path -LiteralPath $TokenFile).Path
    $sharedTokenFileName = "server.token"
    $configFileName = if ([string]::IsNullOrWhiteSpace($Server)) { "client.local.conf" } else { "client.public.conf" }
    $transportSecretValue = Resolve-SyncVisionClientConfigTransportSecret `
        -TransportEncryption $TransportEncryption `
        -TransportSecret $TransportSecret `
        -TransportSecretFile $TransportSecretFile
    $transportSecretFileName = if ($TransportEncryption -eq "disabled") { "" } else { "transport.secret" }
    $hostDeviceTokenFileName = ""
    $viewerDeviceTokenFileName = ""
    if (-not [string]::IsNullOrWhiteSpace($HostDeviceTokenFile)) {
        Assert-SyncVisionClientConfigTokenFile -TokenFile $HostDeviceTokenFile
        $hostDeviceTokenFileName = "host.device.token"
    }
    if (-not [string]::IsNullOrWhiteSpace($ViewerDeviceTokenFile)) {
        Assert-SyncVisionClientConfigTokenFile -TokenFile $ViewerDeviceTokenFile
        $viewerDeviceTokenFileName = "viewer.device.token"
    }

    $configContent = New-SyncVisionClientPublicConfigContent `
        -Server $Server `
        -TokenFileName $sharedTokenFileName `
        -TransportEncryption $TransportEncryption `
        -TransportSecretFileName $transportSecretFileName `
        -IncludeDeviceTokenTemplates:$IncludeDeviceTokenTemplates
    $hostStartContent = New-SyncVisionGeneratedStarterContent `
        -Mode host `
        -ClientConfigFileName $configFileName `
        -HostDevice $HostDevice `
        -ViewerDevice $ViewerDevice `
        -DeviceTokenFileName $hostDeviceTokenFileName `
        -IncludeDeviceTokenTemplates:$IncludeDeviceTokenTemplates
    $viewerStartContent = New-SyncVisionGeneratedStarterContent `
        -Mode viewer `
        -ClientConfigFileName $configFileName `
        -HostDevice $HostDevice `
        -ViewerDevice $ViewerDevice `
        -DeviceTokenFileName $viewerDeviceTokenFileName `
        -IncludeDeviceTokenTemplates:$IncludeDeviceTokenTemplates

    $summaryLines = New-Object System.Collections.Generic.List[string]
    $summaryLines.Add("SyncVision client config generation plan:")
    $summaryLines.Add("  OutputDir: $OutputDir")
    $summaryLines.Add("  ClientConfig: $configFileName")
    $summaryLines.Add("  SharedTokenFile: $sharedTokenFileName")
    $summaryLines.Add("  HostStarter: start-host.generated.ps1")
    $summaryLines.Add("  ViewerStarter: start-viewer.generated.ps1")
    $summaryLines.Add("  Server: $(if ([string]::IsNullOrWhiteSpace($Server)) { "local/LAN discovery fallback" } else { $Server })")
    $summaryLines.Add("  HostDevice: $HostDevice")
    $summaryLines.Add("  ViewerDevice: $ViewerDevice")
    if ($TransportEncryption -ne "disabled") {
        $summaryLines.Add("  TransportEncryption: $TransportEncryption")
        $summaryLines.Add("  TransportSecretFile: $transportSecretFileName")
    }
    if (-not [string]::IsNullOrWhiteSpace($hostDeviceTokenFileName)) {
        $summaryLines.Add("  HostDeviceTokenFile: $hostDeviceTokenFileName")
    }
    if (-not [string]::IsNullOrWhiteSpace($viewerDeviceTokenFileName)) {
        $summaryLines.Add("  ViewerDeviceTokenFile: $viewerDeviceTokenFileName")
    }

    [pscustomobject]@{
        Server = $Server
        OutputDir = $OutputDir
        SourceTokenFile = $resolvedTokenFile
        TokenFileName = $sharedTokenFileName
        TokenFilePath = Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName $sharedTokenFileName
        TransportSecret = $transportSecretValue
        TransportSecretFileName = $transportSecretFileName
        TransportSecretPath = if ([string]::IsNullOrWhiteSpace($transportSecretFileName)) { "" } else { Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName $transportSecretFileName }
        ConfigFileName = $configFileName
        ConfigPath = Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName $configFileName
        ConfigContent = $configContent
        HostDevice = $HostDevice
        ViewerDevice = $ViewerDevice
        HostDeviceTokenSource = $HostDeviceTokenFile
        HostDeviceTokenFileName = $hostDeviceTokenFileName
        HostDeviceTokenPath = if ([string]::IsNullOrWhiteSpace($hostDeviceTokenFileName)) { "" } else { Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName $hostDeviceTokenFileName }
        ViewerDeviceTokenSource = $ViewerDeviceTokenFile
        ViewerDeviceTokenFileName = $viewerDeviceTokenFileName
        ViewerDeviceTokenPath = if ([string]::IsNullOrWhiteSpace($viewerDeviceTokenFileName)) { "" } else { Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName $viewerDeviceTokenFileName }
        HostStartPath = Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName "start-host.generated.ps1"
        HostStartContent = $hostStartContent
        ViewerStartPath = Resolve-SyncVisionClientConfigOutputPath -OutputDir $OutputDir -FileName "start-viewer.generated.ps1"
        ViewerStartContent = $viewerStartContent
        SummaryContent = (($summaryLines -join [Environment]::NewLine) + [Environment]::NewLine)
    }
}

function Write-SyncVisionClientConfigPlan {
    param(
        [Parameter(Mandatory = $true)]
        $Plan,

        [switch]$Force
    )

    New-Item -ItemType Directory -Force -Path $Plan.OutputDir | Out-Null
    $targets = @(
        $Plan.TokenFilePath,
        $Plan.ConfigPath,
        $Plan.HostStartPath,
        $Plan.ViewerStartPath
    )
    if (-not [string]::IsNullOrWhiteSpace($Plan.HostDeviceTokenPath)) {
        $targets += $Plan.HostDeviceTokenPath
    }
    if (-not [string]::IsNullOrWhiteSpace($Plan.ViewerDeviceTokenPath)) {
        $targets += $Plan.ViewerDeviceTokenPath
    }
    if (-not [string]::IsNullOrWhiteSpace($Plan.TransportSecretPath)) {
        $targets += $Plan.TransportSecretPath
    }
    if (-not $Force) {
        foreach ($target in $targets) {
            if (Test-Path -LiteralPath $target) {
                throw "Output file already exists: $target. Pass -Force to overwrite."
            }
        }
    }

    Copy-Item -LiteralPath $Plan.SourceTokenFile -Destination $Plan.TokenFilePath -Force:$Force
    if (-not [string]::IsNullOrWhiteSpace($Plan.HostDeviceTokenPath)) {
        Copy-Item -LiteralPath $Plan.HostDeviceTokenSource -Destination $Plan.HostDeviceTokenPath -Force:$Force
    }
    if (-not [string]::IsNullOrWhiteSpace($Plan.ViewerDeviceTokenPath)) {
        Copy-Item -LiteralPath $Plan.ViewerDeviceTokenSource -Destination $Plan.ViewerDeviceTokenPath -Force:$Force
    }
    if (-not [string]::IsNullOrWhiteSpace($Plan.TransportSecretPath)) {
        [IO.File]::WriteAllText($Plan.TransportSecretPath, $Plan.TransportSecret + [Environment]::NewLine, [Text.UTF8Encoding]::new($false))
    }
    [IO.File]::WriteAllText($Plan.ConfigPath, $Plan.ConfigContent, [Text.UTF8Encoding]::new($false))
    [IO.File]::WriteAllText($Plan.HostStartPath, $Plan.HostStartContent, [Text.UTF8Encoding]::new($false))
    [IO.File]::WriteAllText($Plan.ViewerStartPath, $Plan.ViewerStartContent, [Text.UTF8Encoding]::new($false))

    return $Plan
}

if ($TestMode) {
    return
}

$plan = New-SyncVisionClientConfigPlan `
    -Server $Server `
    -TokenFile $TokenFile `
    -OutputDir $OutputDir `
    -HostDevice $HostDevice `
    -ViewerDevice $ViewerDevice `
    -HostDeviceTokenFile $HostDeviceTokenFile `
    -ViewerDeviceTokenFile $ViewerDeviceTokenFile `
    -TransportEncryption $TransportEncryption `
    -TransportSecret $TransportSecret `
    -TransportSecretFile $TransportSecretFile `
    -IncludeDeviceTokenTemplates:$IncludeDeviceTokenTemplates

Write-SyncVisionClientConfigPlan -Plan $plan -Force:$Force | Out-Null
Write-Host $plan.SummaryContent
