#requires -Version 5
<#
.SYNOPSIS
    Builds every tweak source folder under ..\pack\tweaks\ into a data-only
    mod jar and drops it into ..\pack\overrides\mods\.

.DESCRIPTION
    Each subfolder of ..\pack\tweaks\ is treated as one self-contained
    "data-only" NeoForge mod. Its modId, version, and display name come from
    the folder's META-INF\neoforge.mods.toml. The script:

      1. Reads modId + version from neoforge.mods.toml.
      2. Zips the folder contents (data\, META-INF\, pack.mcmeta) into a jar
         named "<modId>-<version>.jar".
      3. Removes any older jars for the same modId from the output folder so
         stale versions don't get bundled into the manifest.

    Run this before Build-Pack.ps1, or let Build-Pack.ps1 invoke it for you.

.PARAMETER TweaksRoot
    Folder containing tweak source subfolders. Defaults to ..\pack\tweaks\.

.PARAMETER OutputRoot
    Where built jars go. Defaults to ..\pack\overrides\mods\. This path
    becomes Build-Pack.ps1's -LocalPackSource (one level up).
#>

[CmdletBinding()]
param(
    [string]$TweaksRoot = "",
    [string]$OutputRoot = ""
)

$ErrorActionPreference = 'Stop'
$ProgressPreference    = 'SilentlyContinue'

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
if (-not $TweaksRoot) { $TweaksRoot = Join-Path $here '..\pack\tweaks' }
if (-not $OutputRoot) { $OutputRoot = Join-Path $here '..\pack\overrides\mods' }

if (-not (Test-Path $TweaksRoot)) {
    Write-Host "No tweaks folder at $TweaksRoot -- nothing to build."
    return
}

if (-not (Test-Path $OutputRoot)) {
    New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null
}

# Read modId + version out of a neoforge.mods.toml. We do this with a tiny regex
# parser instead of a full TOML library because we only need two scalars and we
# control the file format. Lines like:  modId = "foo"   version = "1.2.3"
function Get-ModMeta {
    param([string]$TomlPath)
    if (-not (Test-Path $TomlPath)) {
        throw "Missing $TomlPath -- every tweak folder needs META-INF\neoforge.mods.toml."
    }
    $content = Get-Content $TomlPath -Raw

    $modIdMatch = [regex]::Match($content, '(?m)^\s*modId\s*=\s*"([^"]+)"')
    $versionMatch = [regex]::Match($content, '(?m)^\s*version\s*=\s*"([^"]+)"')

    if (-not $modIdMatch.Success)   { throw "Could not parse modId from $TomlPath" }
    if (-not $versionMatch.Success) { throw "Could not parse version from $TomlPath" }

    return [pscustomobject]@{
        ModId   = $modIdMatch.Groups[1].Value
        Version = $versionMatch.Groups[1].Value
    }
}

Add-Type -AssemblyName System.IO.Compression.FileSystem

$tweakDirs = Get-ChildItem -Path $TweaksRoot -Directory

if ($tweakDirs.Count -eq 0) {
    Write-Host "No tweak folders found under $TweaksRoot."
    return
}

Write-Host ""
Write-Host "Building tweak jars from $TweaksRoot"
Write-Host ""

foreach ($dir in $tweakDirs) {
    $tomlPath = Join-Path $dir.FullName 'META-INF\neoforge.mods.toml'
    $meta = Get-ModMeta -TomlPath $tomlPath

    $jarName = "$($meta.ModId)-$($meta.Version).jar"
    $jarPath = Join-Path $OutputRoot $jarName

    # Wipe stale jars for this modId so old versions don't get bundled into
    # the manifest alongside the new one.
    Get-ChildItem -Path $OutputRoot -Filter "$($meta.ModId)-*.jar" -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -ne $jarName } |
        ForEach-Object {
            Write-Host ("  removing stale: {0}" -f $_.Name)
            Remove-Item $_.FullName -Force
        }

    if (Test-Path $jarPath) { Remove-Item $jarPath -Force }

    # NB: PowerShell 5.1's [ZipFile]::CreateFromDirectory() writes Windows-
    # native path separators (\) into ZIP entry names on Windows, producing
    # entries like "META-INF\neoforge.mods.toml" instead of the spec-required
    # "META-INF/neoforge.mods.toml". NeoForge's mod scanner then can't find
    # the manifest and silently rejects the jar with "not a valid mod file".
    # We build the archive manually so we control the entry name format.
    # 'Create' string maps to ZipArchiveMode.Create -- PowerShell 5.1 doesn't
    # auto-load the enum type, so the literal string form is more portable.
    $zip = [System.IO.Compression.ZipFile]::Open($jarPath, 'Create')
    try {
        $sourceLen = $dir.FullName.Length
        Get-ChildItem -Path $dir.FullName -Recurse -File | ForEach-Object {
            $relPath = $_.FullName.Substring($sourceLen).TrimStart('\','/').Replace('\','/')
            $entry = $zip.CreateEntry($relPath, [System.IO.Compression.CompressionLevel]::Optimal)
            $entryStream = $entry.Open()
            try {
                $bytes = [System.IO.File]::ReadAllBytes($_.FullName)
                $entryStream.Write($bytes, 0, $bytes.Length)
            } finally { $entryStream.Dispose() }
        }
    } finally { $zip.Dispose() }

    # ── Post-build validation ────────────────────────────────────────────────
    # Catch structural problems BEFORE the jar gets bundled into the manifest
    # and shipped to clients. NeoForge silently rejects invalid jars with a
    # bland "not a valid mod file" warning that's easy to miss in a 500-line
    # game log; this fails the build loudly with a specific reason instead.
    $verify = [System.IO.Compression.ZipFile]::OpenRead($jarPath)
    try {
        $names = @($verify.Entries | ForEach-Object { $_.FullName })

        # 1. No backslashes in entry names. PowerShell 5.1's CreateFromDirectory
        #    leaks Windows path separators into ZIP entries, which makes
        #    NeoForge fail to find META-INF/neoforge.mods.toml. We build with
        #    explicit CreateEntry above, but if anyone "simplifies" the zip
        #    path back to CreateFromDirectory, this guard fires.
        $badSep = $names | Where-Object { $_ -match '\\' }
        if ($badSep) {
            throw ("$jarName has entries with backslash separators (NeoForge will reject it):`n  - " + ($badSep -join "`n  - "))
        }

        # 2. META-INF/neoforge.mods.toml must be at the canonical path.
        if (-not ($names -contains 'META-INF/neoforge.mods.toml')) {
            throw "$jarName is missing META-INF/neoforge.mods.toml at the canonical forward-slash path. Found: $($names -join ', ')"
        }

        # 3. modId from the embedded toml must match the jar's filename, so a
        #    misnamed source folder doesn't ship a jar whose manifest declares
        #    a different mod (which loads silently under the wrong identity).
        $tomlEntry = $verify.GetEntry('META-INF/neoforge.mods.toml')
        $reader = New-Object System.IO.StreamReader($tomlEntry.Open())
        try { $tomlContent = $reader.ReadToEnd() } finally { $reader.Dispose() }
        $tomlModId = [regex]::Match($tomlContent, '(?m)^\s*modId\s*=\s*"([^"]+)"').Groups[1].Value
        if ($tomlModId -ne $meta.ModId) {
            throw "$jarName modId mismatch: source folder advertises '$($meta.ModId)' but built jar's neoforge.mods.toml says '$tomlModId'."
        }
    } finally { $verify.Dispose() }

    $size = (Get-Item $jarPath).Length
    Write-Host ("  built {0,-40} {1,8:N0} bytes  [validated]" -f $jarName, $size)
}

Write-Host ""
Write-Host "Tweak jars are in: $OutputRoot"
