# Loaded from the installed updater, never from a downloaded release script.
$configKeys=@{
 'config/block_factorys_bosses-client.toml'=@('"Will there be a GUI element displaying current roll count"')
 'config/xaero/minimap/profiles/default.cfg'=@('display_radar','tracked_players_on_minimap','tracked_players_in_world')
 'config/xaero/world-map/profiles/default.cfg'=@('display_minimap_radar','display_tracked_players')
}
$radarPath='config/xaero/minimap/profiles/entity_radar_categories/default.cfg.json'
$packPaths=@('options.txt','config/resourcepackoverrides.json')
function Read-PackList([string]$Path,[string]$Text){
 if($Path -eq 'options.txt'){
  $matches=[regex]::Matches($Text,'(?m)^resourcePacks:([^\r\n]*)')
  if($matches.Count -ne 1){throw 'Expected one resourcePacks setting.'}
  $parsed=$matches[0].Groups[1].Value|ConvertFrom-Json
  return ,@($parsed)
 }
 $data=$Text|ConvertFrom-Json
 if(!$data.PSObject.Properties['default_packs']){throw 'Resource pack defaults missing.'}
 return ,@($data.default_packs)
}
function Write-PackList([string]$Path,[string]$Text,$Packs){
 if($Path -eq 'options.txt'){
  $json=ConvertTo-Json -InputObject @($Packs) -Compress
  return [regex]::Replace($Text,'(?m)^resourcePacks:[^\r\n]*',[Text.RegularExpressions.MatchEvaluator]{param($m)'resourcePacks:'+$json})
 }
 $data=$Text|ConvertFrom-Json;$data.default_packs=@($Packs)
 return ($data|ConvertTo-Json -Depth 40)
}
function Config-Target([string]$Relative){
 if(!$configKeys.ContainsKey($Relative) -and $Relative -ne $radarPath -and $Relative -notin $packPaths){throw 'Unapproved config file.'}
 $p=[IO.Path]::GetFullPath((Join-Path $game $Relative));$walk=$p
 while($walk -and $walk.Length -ge $game.Length){if((Test-Path -LiteralPath $walk) -and ((Get-Item -LiteralPath $walk).Attributes -band [IO.FileAttributes]::ReparsePoint)){throw 'Linked config paths are not supported.'};$walk=Split-Path $walk -Parent}
 return $p
}
function Radar-Node($Data){
 $matches=New-Object Collections.ArrayList
 function Walk($node){if($node.hardInclude -eq 'players'){[void]$matches.Add($node)};foreach($child in $node.subCategories){Walk $child}}
 Walk $Data;if($matches.Count -ne 1){throw 'Expected one player radar category.'};return $matches[0]
}
function Prepare-Config($File){
 $p=Config-Target $File.path;if(!(Test-Path -LiteralPath $p -PathType Leaf)){throw ('Config missing: '+$File.path)}
 $before=[IO.File]::ReadAllBytes($p);$text=$utf8.GetString($before).TrimStart([char]0xFEFF);$fields=@();$seen=@{}
 if($File.path -in $packPaths){
  if($File.kind -ne 'optional-excalibur' -or @($File.changes).Count){throw 'Invalid resource pack migration.'}
  $packs=Read-PackList $File.path $text;$kept=@();$removed=@();$index=0
  foreach($pack in $packs){if([string]$pack -match '^file/Excalibur[^/\\]*\.zip$'){$removed+=@{name=[string]$pack;index=$index}}else{$kept+=$pack};$index++}
  $fields=@(@{key='excalibur';before=$removed;after=@()})
  $after=if($removed.Count){$utf8.GetBytes((Write-PackList $File.path $text $kept))}else{$before}
 }elseif($File.path -eq $radarPath){
  if($File.kind -ne 'player-radar' -or @($File.changes).Count){throw 'Invalid radar migration.'}
  $data=$text|ConvertFrom-Json;$node=Radar-Node $data
  if(!$node.settingOverrides){throw 'Player radar settingOverrides is missing.'}
  foreach($key in @('displayed','icons','names','alwaysDisplayNametags')){
   $value=switch($key){'displayed'{$true};'icons'{2};'names'{1};'alwaysDisplayNametags'{$true}}
   $property=$node.settingOverrides.PSObject.Properties[$key]
   $fields+=@{key=$key;existed=($null -ne $property);before=if($property){$property.Value}else{$null};after=$value}
   $node.settingOverrides|Add-Member NoteProperty $key $value -Force
  }
  $after=$utf8.GetBytes(($data|ConvertTo-Json -Depth 40))
 }else{
  if($File.kind -ne 'lines' -or !@($File.changes).Count){throw 'Invalid config migration.'}
  foreach($change in $File.changes){
   if($change.key -cnotin $configKeys[$File.path] -or $seen.ContainsKey($change.key) -or [string]$change.value -notin @('true','false')){throw 'Unapproved config setting/value.'};$seen[$change.key]=$true
   $rx='(?m)^(\s*'+[regex]::Escape($change.key)+'\s*=\s*)(true|false)([ \t]*(?:#[^\r\n]*)?)(?=\r?$)';$found=[regex]::Matches($text,$rx)
   if($found.Count -ne 1){throw ('Expected one boolean setting: '+$change.key)}
   $fields+=@{key=$change.key;existed=$true;before=$found[0].Groups[2].Value;after=[string]$change.value}
   $text=[regex]::Replace($text,$rx,[Text.RegularExpressions.MatchEvaluator]{param($m)$m.Groups[1].Value+[string]$change.value+$m.Groups[3].Value})
  }
  $after=$utf8.GetBytes($text)
 }
 return @{path=$File.path;target=$p;present=$true;before=(Digest $before);after=(Digest $after);bytes=$after;fields=$fields;config=$true;remove=$false}
}
function Restore-ConfigBytes($Operation){
 $p=Config-Target $Operation.path;if(!(Test-Path -LiteralPath $p)){throw 'Config removed after migration; backup retained.'}
 $bytes=[IO.File]::ReadAllBytes($p);if((Digest $bytes) -eq $Operation.before){return ,$bytes}
 $text=$utf8.GetString($bytes).TrimStart([char]0xFEFF)
 if($Operation.path -in $packPaths){
  if(@($Operation.fields).Count -ne 1 -or $Operation.fields[0].key -ne 'excalibur'){throw 'Invalid resource pack rollback.'}
  $packs=New-Object Collections.ArrayList
  foreach($pack in (Read-PackList $Operation.path $text)){[void]$packs.Add($pack)}
  foreach($removed in @($Operation.fields[0].before)){
   if($removed.name -notmatch '^file/Excalibur[^/\\]*\.zip$' -or [int]$removed.index -lt 0){throw 'Invalid resource pack rollback entry.'}
   if($removed.name -notin $packs){$packs.Insert([Math]::Min([int]$removed.index,$packs.Count),[string]$removed.name)}
  }
  return ,($utf8.GetBytes((Write-PackList $Operation.path $text @($packs))))
 }
 if($Operation.path -eq $radarPath){
  $data=$text|ConvertFrom-Json;$node=Radar-Node $data
  foreach($field in $Operation.fields){
   if($field.key -notin @('displayed','icons','names','alwaysDisplayNametags')){throw 'Invalid radar rollback field.'}
   $property=$node.settingOverrides.PSObject.Properties[$field.key]
   $current=if($property){$property.Value}else{$null}
   if($current -eq $field.before -and [bool]$field.existed -eq ($null -ne $property)){continue}
   if(!$property -or $current -ne $field.after){throw ('Managed radar setting changed after update: '+$field.key)}
   if($field.existed){$property.Value=$field.before}else{$node.settingOverrides.PSObject.Properties.Remove($field.key)}
  }
  return ,($utf8.GetBytes(($data|ConvertTo-Json -Depth 40)))
 }
 foreach($field in $Operation.fields){
  if($field.key -cnotin $configKeys[$Operation.path] -or $field.before -notin @('true','false')){throw 'Invalid config rollback field.'}
  $rx='(?m)^(\s*'+[regex]::Escape($field.key)+'\s*=\s*)(true|false)([ \t]*(?:#[^\r\n]*)?)(?=\r?$)';$found=[regex]::Matches($text,$rx)
  if($found.Count -ne 1){throw 'Config format changed; backup retained.'}
  if($found[0].Groups[2].Value -eq $field.before){continue}
  if($found[0].Groups[2].Value -ne $field.after){throw 'Managed setting changed after migration; backup retained.'}
  $text=[regex]::Replace($text,$rx,[Text.RegularExpressions.MatchEvaluator]{param($m)$m.Groups[1].Value+$field.before+$m.Groups[3].Value})
 }
 return ,($utf8.GetBytes($text))
}
