# OnTheBlock one-click installer (GUI). Ships INSIDE the supporter zip, beside the
# files it installs. The user unzips, double-clicks Install-OnTheBlock.bat, pastes
# their keys, clicks Install - it finds GTA V, copies every mod file into scripts\,
# and writes their keys into OnTheBlock.ini. No manual file-copying or INI editing.
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# The folder this script lives in = the unzipped supporter folder (has the DLLs + ini).
$SRC = Split-Path -Parent $MyInvocation.MyCommand.Path
# Files we install (only those that exist in the zip are copied).
$FILES = @("OnTheBlock.dll","OnTheBlock.ini","NAudio.dll","iFruitAddon2.dll","NativeUI.dll","iFruitAddon2_CrashFix.dll","0Harmony.dll")

function Find-GTA {
  $cands = @()
  # Rockstar registry (Steam + Epic + Rockstar launcher all write one of these)
  foreach ($k in @(
    "HKLM:\SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V",
    "HKLM:\SOFTWARE\Rockstar Games\Grand Theft Auto V",
    "HKLM:\SOFTWARE\WOW6432Node\Rockstar Games\GTAV",
    "HKLM:\SOFTWARE\WOW6432Node\Rockstar Games\Grand Theft Auto V Enhanced")) {
    try { $v = (Get-ItemProperty -Path $k -ErrorAction Stop).InstallFolder; if ($v) { $cands += $v } } catch {}
    try { $v = (Get-ItemProperty -Path $k -ErrorAction Stop).InstallFolderSteam; if ($v) { $cands += ($v -replace '\\GTAV\\?$','') } } catch {}
  }
  # Common install locations + every Steam library
  $cands += @(
    "C:\Program Files (x86)\Steam\steamapps\common\Grand Theft Auto V",
    "C:\Program Files\Epic Games\GTAV",
    "C:\Program Files\Rockstar Games\Grand Theft Auto V",
    "D:\SteamLibrary\steamapps\common\Grand Theft Auto V")
  try {
    $vdf = "C:\Program Files (x86)\Steam\steamapps\libraryfolders.vdf"
    if (Test-Path $vdf) {
      foreach ($m in ([regex]::Matches((Get-Content $vdf -Raw), '"path"\s+"([^"]+)"'))) {
        $cands += (Join-Path ($m.Groups[1].Value -replace '\\\\','\') "steamapps\common\Grand Theft Auto V")
      }
    }
  } catch {}
  foreach ($c in $cands) { if ($c -and (Test-Path (Join-Path $c "GTA5.exe")) -or ($c -and (Test-Path (Join-Path $c "GTA5_Enhanced.exe")))) { return $c } }
  return ""
}

function Set-IniKey([string]$ini, [string]$key, [string]$val) {
  if ([string]::IsNullOrWhiteSpace($val)) { return $ini }
  $esc = [regex]::Escape($key)
  if ($ini -match "(?m)^\s*$esc\s*=") { return [regex]::Replace($ini, "(?m)^(\s*$esc\s*=).*$", "`${1}$val") }
  return $ini  # key not present in shipped ini -> leave structure as-is
}

function Disable-StrayOnTheBlockDlls([string]$scripts) {
  if (-not (Test-Path $scripts)) { return @() }
  $disabled = Join-Path $scripts "OnTheBlock_Backups\Disabled_Root_DLLs"
  New-Item -ItemType Directory -Force -Path $disabled | Out-Null
  $moved = @()
  $patterns = @("LSlife.dll", "OnTheBlock_v*.dll", "OnTheBlock_*backup*.dll", "OnTheBlock_*before*.dll")
  foreach ($pattern in $patterns) {
    foreach ($f in Get-ChildItem -LiteralPath $scripts -Filter $pattern -File -ErrorAction SilentlyContinue) {
      if ($f.Name -ieq "OnTheBlock.dll") { continue }
      try {
        $stamp = Get-Date -Format "yyyyMMdd_HHmmss"
        $dest = Join-Path $disabled ($f.BaseName + "_" + $stamp + $f.Extension + ".disabled")
        Move-Item -LiteralPath $f.FullName -Destination $dest -Force
        $moved += $f.Name
      } catch {
        $moved += ($f.Name + " (could not move; close GTA fully and run installer again)")
      }
    }
  }
  return $moved
}

# Installs the Real Life graphics layer. Bundles only the tiny preset + a 7-Zip
# extractor; downloads the ReShade runtime (reshade.me) and shaders (github) at
# install so the zip stays small enough for Discord. Fails gracefully: returns a
# message and the mod still works. Returns "OK" on success.
function Install-ReShade([string]$g, [string]$src) {
  $rs = Join-Path $src "reshade"
  if (-not (Test-Path (Join-Path $rs "OTB_RealLife_ReShade.ini"))) { return "graphics files not in this download" }
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
  $tmp = Join-Path $env:TEMP ("otb_reshade_" + [System.Guid]::NewGuid().ToString("N").Substring(0,8))
  New-Item -ItemType Directory -Force $tmp | Out-Null
  try {
    # 1) shaders from github (a real zip; no extra tools needed)
    $shZip = Join-Path $tmp "shaders.zip"
    Invoke-WebRequest -Uri "https://github.com/crosire/reshade-shaders/archive/refs/heads/legacy.zip" -OutFile $shZip -UseBasicParsing -TimeoutSec 120
    Expand-Archive $shZip -DestinationPath $tmp -Force
    $shSrc = Get-ChildItem $tmp -Directory | Where-Object { $_.Name -like "reshade-shaders-*" } | Select-Object -First 1
    if (-not $shSrc) { return "shader download did not unpack; install ReShade manually from reshade.me" }
    $shDst = Join-Path $g "reshade-shaders"
    if (Test-Path $shDst) { Remove-Item $shDst -Recurse -Force -ErrorAction SilentlyContinue }
    New-Item -ItemType Directory -Force $shDst | Out-Null
    Copy-Item (Join-Path $shSrc.FullName "Shaders") $shDst -Recurse -Force
    if (Test-Path (Join-Path $shSrc.FullName "Textures")) { Copy-Item (Join-Path $shSrc.FullName "Textures") $shDst -Recurse -Force }
    # Deband lives in the slim branch (not legacy); our preset uses it, so drop the bundled copy in
    if (Test-Path (Join-Path $rs "Deband.fx")) { Copy-Item (Join-Path $rs "Deband.fx") (Join-Path $shDst "Shaders\Deband.fx") -Force }
    # 2) ReShade runtime from reshade.me; extract the DLL with the bundled 7-Zip
    $page = (Invoke-WebRequest -Uri "https://reshade.me" -UseBasicParsing -UserAgent "Mozilla/5.0" -TimeoutSec 60).Content
    $m = [regex]::Match($page, 'ReShade_Setup_[0-9.]+_Addon\.exe')
    $setupName = if ($m.Success) { $m.Value } else { "ReShade_Setup_6.7.3_Addon.exe" }
    $setup = Join-Path $tmp $setupName
    Invoke-WebRequest -Uri ("https://reshade.me/downloads/" + $setupName) -OutFile $setup -UseBasicParsing -UserAgent "Mozilla/5.0" -TimeoutSec 120
    & (Join-Path $rs "7z.exe") e $setup "ReShade64.dll" ("-o" + $tmp) -y | Out-Null
    $dll = Join-Path $tmp "ReShade64.dll"
    if (-not (Test-Path $dll)) { return "could not unpack ReShade; install it manually from reshade.me (the preset + shaders are already placed)" }
    $dxgi = Join-Path $g "dxgi.dll"
    if (Test-Path $dxgi) { try { Copy-Item $dxgi "$dxgi.bak" -Force } catch {} }
    Copy-Item $dll $dxgi -Force
    try { Unblock-File $dxgi -ErrorAction SilentlyContinue } catch {}
    # 3) our preset + a ReShade.ini that auto-loads it, paths baked to THIS folder
    Copy-Item (Join-Path $rs "OTB_RealLife_ReShade.ini") (Join-Path $g "OTB_RealLife_ReShade.ini") -Force
    $nl = "`r`n"
    $ini = "[GENERAL]" + $nl +
      "EffectSearchPaths=" + (Join-Path $g "reshade-shaders\Shaders") + $nl +
      "TextureSearchPaths=" + (Join-Path $g "reshade-shaders\Textures") + $nl +
      "PresetPath=" + (Join-Path $g "OTB_RealLife_ReShade.ini") + $nl +
      "PerformanceMode=0" + $nl +
      "PreprocessorDefinitions=RESHADE_DEPTH_INPUT_IS_REVERSED=1" + $nl +
      "SkipLoadingDisabledEffects=0" + $nl + $nl +
      "[INPUT]" + $nl + "KeyOverlay=36,0,0,0" + $nl + $nl +
      "[OVERLAY]" + $nl + "TutorialProgress=4" + $nl
    [System.IO.File]::WriteAllText((Join-Path $g "ReShade.ini"), $ini)
    return "OK"
  } catch {
    return "graphics download failed (" + $_.Exception.Message + "); the mod still works, you can install ReShade later from reshade.me"
  } finally {
    try { Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue } catch {}
  }
}

# ---------- UI ----------
$f = New-Object Windows.Forms.Form
$f.Text = "OnTheBlock Installer"
$f.Size = New-Object Drawing.Size(560, 590)
$f.StartPosition = "CenterScreen"
$f.FormBorderStyle = "FixedDialog"; $f.MaximizeBox = $false

function Lbl($t,$x,$y,$w,$bold){ $l=New-Object Windows.Forms.Label; $l.Text=$t; $l.Location=New-Object Drawing.Point($x,$y); $l.Size=New-Object Drawing.Size($w,18); if($bold){$l.Font=New-Object Drawing.Font($l.Font,[Drawing.FontStyle]::Bold)}; $f.Controls.Add($l); return $l }
function Box($x,$y,$w){ $t=New-Object Windows.Forms.TextBox; $t.Location=New-Object Drawing.Point($x,$y); $t.Size=New-Object Drawing.Size($w,22); $f.Controls.Add($t); return $t }

Lbl "GTA V folder" 20 18 520 $true | Out-Null
$gtaBox = Box 20 40 420
$gtaBox.Text = (Find-GTA)
$browse = New-Object Windows.Forms.Button; $browse.Text="Browse"; $browse.Location=New-Object Drawing.Point(450,39); $browse.Size=New-Object Drawing.Size(80,24); $f.Controls.Add($browse)
$status = Lbl "" 20 66 520 $false

Lbl "License key  (required - the key the bot/Chris DM'd you)" 20 100 520 $true | Out-Null
$licBox = Box 20 122 510
Lbl "Discord ID  (required - your numeric Discord user id)" 20 152 520 $true | Out-Null
$didBox = Box 20 174 510

Lbl "AI keys (optional - for talking to NPCs; you can add these later)" 20 212 520 $true | Out-Null
Lbl "Anthropic API key  (the NPC brain)" 20 236 520 $false | Out-Null
$antBox = Box 20 256 510
Lbl "Cartesia API key  (spoken voice)" 20 286 520 $false | Out-Null
$carBox = Box 20 306 510
Lbl "Groq API key  (talk back with your mic - free at console.groq.com)" 20 336 520 $false | Out-Null
$grqBox = Box 20 356 510

$reshadeChk = New-Object Windows.Forms.CheckBox
$reshadeChk.Text = "Also install the Real Life graphics layer (ReShade preset) - recommended"
$reshadeChk.Location = New-Object Drawing.Point(20,384); $reshadeChk.Size = New-Object Drawing.Size(515,22); $reshadeChk.Checked = $true
$f.Controls.Add($reshadeChk)

$install = New-Object Windows.Forms.Button; $install.Text="Install"; $install.Location=New-Object Drawing.Point(20,412); $install.Size=New-Object Drawing.Size(510,40)
$install.Font = New-Object Drawing.Font($install.Font, [Drawing.FontStyle]::Bold); $f.Controls.Add($install)
$out = Lbl "" 20 460 520 $false; $out.Size = New-Object Drawing.Size(520, 50)

# Legacy vs Enhanced detection by which exe is present (Enhanced = GTA5_Enhanced.exe).
function Get-Edition($g) {
  if (-not $g) { return "" }
  if (Test-Path (Join-Path $g "GTA5_Enhanced.exe")) { return "Enhanced" }
  if (Test-Path (Join-Path $g "GTA5.exe")) { return "Legacy" }
  return ""
}
function Refresh-Status {
  $g = $gtaBox.Text
  $ed = Get-Edition $g
  if ($ed) {
    $shv = Test-Path (Join-Path $g "ScriptHookV.dll")
    $shvdn = (Test-Path (Join-Path $g "ScriptHookVDotNet.asi")) -or (Test-Path (Join-Path $g "ScriptHookVDotNet3.dll"))
    $status.Text = "GTA V (" + $ed + ") found.  ScriptHookV: " + ($(if($shv){"yes"}else{"MISSING"})) + "   ScriptHookVDotNet: " + ($(if($shvdn){"yes"}else{"MISSING"}))
    $status.ForeColor = $(if($shv -and $shvdn){[Drawing.Color]::Green}else{[Drawing.Color]::DarkOrange})
  } else { $status.Text = "GTA V not found here - click Browse and pick your Grand Theft Auto V folder."; $status.ForeColor = [Drawing.Color]::Red }
}
Refresh-Status
$gtaBox.Add_TextChanged({ Refresh-Status })
$browse.Add_Click({
  $d = New-Object Windows.Forms.FolderBrowserDialog; $d.Description = "Pick your Grand Theft Auto V folder"
  if ($d.ShowDialog() -eq "OK") { $gtaBox.Text = $d.SelectedPath }
})

$install.Add_Click({
  $g = $gtaBox.Text
  if (-not ($g -and ((Test-Path (Join-Path $g "GTA5.exe")) -or (Test-Path (Join-Path $g "GTA5_Enhanced.exe"))))) { [Windows.Forms.MessageBox]::Show("That doesn't look like your GTA V folder (no GTA5.exe). Use Browse.","OnTheBlock"); return }
  if (-not $licBox.Text.Trim()) { [Windows.Forms.MessageBox]::Show("Enter your license key.","OnTheBlock"); return }
  if (-not $didBox.Text.Trim()) { [Windows.Forms.MessageBox]::Show("Enter your Discord ID.","OnTheBlock"); return }
  try {
    $scripts = Join-Path $g "scripts"
    if (-not (Test-Path $scripts)) { New-Item -ItemType Directory -Path $scripts | Out-Null }
    $disabledStrays = Disable-StrayOnTheBlockDlls $scripts
    # Zip extracted straight into scripts\? Then source == destination and
    # Copy-Item dies with "Cannot overwrite the item ... with itself"
    # (supporter report). The files are already where they belong - skip the
    # self-copies and just finish the setup (ini keys, web assets).
    $srcIsScripts = $false
    try { $srcIsScripts = ((Resolve-Path $SRC).Path.TrimEnd('\') -ieq (Resolve-Path $scripts).Path.TrimEnd('\')) } catch {}
    # copy every bundled file that exists beside this installer
    $copied = @()
    foreach ($fn in $FILES) {
      $sp = Join-Path $SRC $fn
      if ($fn -eq "OnTheBlock.ini") { continue } # written separately with the keys
      if (Test-Path $sp) {
        $dst = Join-Path $scripts $fn
        if ($srcIsScripts) { $copied += ($fn + " (already in scripts)"); continue }
        if ((Test-Path $dst) -and ($fn -eq "OnTheBlock.dll")) { Copy-Item $dst "$dst.bak" -Force }
        Copy-Item $sp $dst -Force; $copied += $fn
      }
    }
    # write OnTheBlock.ini with the user's keys
    $iniSrc = Join-Path $SRC "OnTheBlock.ini"
    if (Test-Path $iniSrc) {
      $ini = [System.IO.File]::ReadAllText($iniSrc)
      $ini = Set-IniKey $ini "LICENSE_KEY" $licBox.Text.Trim()
      $ini = Set-IniKey $ini "DISCORD_ID" $didBox.Text.Trim()
      if ($antBox.Text.Trim()) { $ini = Set-IniKey $ini "ANTHROPIC_API_KEY" $antBox.Text.Trim() }
      if ($carBox.Text.Trim()) { $ini = Set-IniKey $ini "CARTESIA_API_KEY" $carBox.Text.Trim(); $ini = Set-IniKey $ini "TTS_ENGINE" "cartesia" }
      if ($grqBox.Text.Trim()) { $ini = Set-IniKey $ini "GROQ_API_KEY" $grqBox.Text.Trim(); $ini = Set-IniKey $ini "STT_ENGINE" "groq" }
      $iniDst = Join-Path $scripts "OnTheBlock.ini"
      if (Test-Path $iniDst) { Copy-Item $iniDst "$iniDst.bak" -Force }
      [System.IO.File]::WriteAllText($iniDst, $ini, (New-Object System.Text.UTF8Encoding($false)))
      $copied += "OnTheBlock.ini (with your keys)"
    }
    # phone OS web assets -> scripts\OnTheBlock\web\ (else "OnTheBlock OS not loaded")
    try {
      $webSrc = Join-Path $SRC "OnTheBlock\web"
      if (Test-Path $webSrc) {
        if ($srcIsScripts) { $copied += "phone OS (web assets already in scripts)" }
        else {
          $webDst = Join-Path $scripts "OnTheBlock\web"
          New-Item -ItemType Directory -Force $webDst | Out-Null
          Copy-Item (Join-Path $webSrc "*") $webDst -Recurse -Force
          $copied += "phone OS (web assets)"
        }
      }
    } catch {}
    # starter data XMLs (dealers, spawns, stash houses, vehicles, weapons,
    # zoneData) -> scripts\OnTheBlock\ - fresh one-click installs were missing
    # these entirely (only DLLs + web were copied; supporter report: no
    # OnTheBlock_StashHouses.xml even on a fresh install). NEVER overwrite an
    # existing file: on an already-played install these hold live player state.
    try {
      $dataSrc = Join-Path $SRC "OnTheBlock"
      if ((Test-Path $dataSrc) -and (-not $srcIsScripts)) {
        $dataDst = Join-Path $scripts "OnTheBlock"
        $added = 0
        foreach ($df in Get-ChildItem -LiteralPath $dataSrc -Recurse -File) {
          if ($df.FullName -like "*\web\*") { continue } # web handled above (always refreshed)
          $rel = $df.FullName.Substring($dataSrc.Length).TrimStart('\')
          $dfDst = Join-Path $dataDst $rel
          if (-not (Test-Path $dfDst)) {
            New-Item -ItemType Directory -Force (Split-Path -Parent $dfDst) | Out-Null
            Copy-Item $df.FullName $dfDst
            $added++
          }
        }
        if ($added -gt 0) { $copied += ("starter data files (" + $added + ")") }
      }
    } catch {}
    # Real Life graphics layer (ReShade) - optional, on by default
    $rsNote = ""
    if ($reshadeChk.Checked) {
      try {
        $rsResult = Install-ReShade $g $SRC
        if ($rsResult -eq "OK") { $copied += "Real Life graphics (ReShade preset)" }
        else { $rsNote = "`n`nGraphics layer skipped: " + $rsResult + " (the mod still works without it)." }
      } catch { $rsNote = "`n`nGraphics layer skipped: " + $_.Exception.Message + " (the mod still works without it)." }
    }
    # clear any stray scripts\OnTheBlock.dll vs LSlife.dll load-order landmine note
    $ed = Get-Edition $g
    $shvMiss = -not (Test-Path (Join-Path $g "ScriptHookV.dll"))
    $shvdnMiss = -not ((Test-Path (Join-Path $g "ScriptHookVDotNet.asi")) -or (Test-Path (Join-Path $g "ScriptHookVDotNet3.dll")))
    $warn = ""
    if ($shvMiss) {
      if ($ed -eq "Enhanced") { $warn += "`n- ScriptHookV is MISSING. You're on GTA V ENHANCED, so get the ScriptHookV for GTA V Enhanced from dev-c.com (the ENHANCED build, NOT the classic one) and drop its files into the GTA folder." }
      else { $warn += "`n- ScriptHookV is MISSING. You're on GTA V LEGACY, so get the classic ScriptHookV from dev-c.com (the LEGACY build, NOT the Enhanced one) and drop the .dll + .asi into the GTA folder." }
    }
    if ($shvdnMiss) { $warn += "`n- ScriptHookVDotNet (nightly) is MISSING. Get the latest v3.7.0-nightly from github.com/crosire/scripthookvdotnet/releases (marked Latest). The same nightly works on both Legacy and Enhanced." }
    # Even when a ScriptHookV is present we can't tell which build it is, so remind about the match (this is the #1 crash-on-load cause).
    $editionTip = ""
    if (-not $shvMiss) { $editionTip = "`n`nReminder: your ScriptHookV must be the " + $ed + " build - the wrong-edition ScriptHookV is the #1 crash-on-load cause." }
    $out.ForeColor = [Drawing.Color]::Green
    $out.Text = "Installed " + $copied.Count + " files into scripts\ (GTA V " + $ed + ").  Fully close GTA V and relaunch."
    $rsTip = $(if ($copied -contains "Real Life graphics (ReShade preset)") { "`n`nGraphics: the Real Life look is on. Press Home in-game to open the ReShade overlay if you want to tweak it. Single player only - never use it on GTA Online." } else { "" })
    $strayTip = $(if ($disabledStrays -and $disabledStrays.Count -gt 0) { "`n`nDisabled old duplicate mod DLLs so SHVDN only loads the current OnTheBlock.dll: " + ($disabledStrays -join ", ") } else { "" })
    [Windows.Forms.MessageBox]::Show("OnTheBlock installed!`n`nCopied: " + ($copied -join ", ") + $strayTip + "`n`nNow FULLY close GTA V (to desktop) and relaunch. Press the in-game phone key (Left Arrow) to start." + $rsTip + $rsNote + $(if($warn){"`n`nBEFORE it works you still need:" + $warn}else{""}) + $editionTip, "OnTheBlock - Done")
  } catch {
    $out.ForeColor = [Drawing.Color]::Red; $out.Text = "Install failed: " + $_.Exception.Message
    [Windows.Forms.MessageBox]::Show("Install failed: " + $_.Exception.Message, "OnTheBlock")
  }
})

[void]$f.ShowDialog()
