param(
    [string]$Source = $PSScriptRoot,
    # When set, no files are copied or deleted -- output shows what *would* happen.
    # Use to verify the destination list, .pkgmeta parsing, and skip patterns
    # before letting the watcher actually mutate target installs.
    [switch]$DryRun
)

$AddonName   = "TOGTools"
$WowBase     = "${env:ProgramFiles(x86)}\World of Warcraft"
# All WoW client versions TOGTools supports -- see the per-flavor .toc files
# (TOGTools.toc, TOGTools_BCC.toc, TOGTools_Wrath.toc, TOGTools_Cata.toc,
# TOGTools_Mists.toc, TOGTools_Mainline.toc). The script copies into every
# version directory that exists on disk, except the one the source tree
# already lives in (avoids copying onto itself).
$WowVersions = @("_classic_era_", "_classic_", "_anniversary_", "_retail_")

# Build list of addon install directories that actually exist on disk
$Destinations = foreach ($ver in $WowVersions) {
    $addonsDir = Join-Path $WowBase "$ver\Interface\AddOns"
    $dest = Join-Path $addonsDir $AddonName
    if ((Test-Path $addonsDir) -and ($Source -notlike "$dest*")) {
        if (-not (Test-Path $dest) -and -not $DryRun) {
            New-Item -ItemType Directory -Path $dest -Force | Out-Null
        }
        $dest
    }
}

if (-not $Destinations) {
    Write-Host "No WoW installation found under $WowBase. Exiting." -ForegroundColor Red
    exit 1
}

# -----------------------------------------------------------------------------
# Skip-pattern construction
#
# The goal is for the synced target installs to look like the BigWigs-packaged
# release: only the files that ship to CurseForge, no git/IDE/dev metadata.
# So the skip list is built from two sources:
#   1) .pkgmeta `ignore:` entries -- the canonical "not in the zip" list.
#      Parsed at script start so updates to .pkgmeta automatically apply.
#   2) Always-skip -- git metadata and the script itself, regardless of .pkgmeta.
# -----------------------------------------------------------------------------

function Convert-GlobToRegex([string]$glob) {
    # Normalize forward slashes to backslashes (file paths come in as Windows-style)
    $g = $glob -replace '/', '\'

    # "**\" prefix -> match anywhere in the tree, not just root
    $matchAnywhere = $false
    if ($g.StartsWith('**\')) {
        $g = $g.Substring(3)
        $matchAnywhere = $true
    }

    # Trailing "\" marks a directory; strip it for the literal match
    $isDir = $g.EndsWith('\')
    if ($isDir) {
        $g = $g.TrimEnd('\')
    }

    # Escape regex specials, then turn glob wildcards back into regex equivalents
    $escaped = [regex]::Escape($g)
    $escaped = $escaped -replace '\\\*', '[^\\]*'   # * -> any segment-internal chars
    $escaped = $escaped -replace '\\\?', '[^\\]'    # ? -> one char

    $prefix = if ($matchAnywhere) { '(^|\\)' } else { '^' }
    $suffix = if ($isDir)         { '(\\|$)' } else { '$' }
    return "$prefix$escaped$suffix"
}

function Get-PkgmetaIgnores([string]$pkgmetaPath) {
    if (-not (Test-Path $pkgmetaPath)) { return @() }
    $ignores  = @()
    $inIgnore = $false
    foreach ($raw in Get-Content $pkgmetaPath) {
        # Strip comments
        $line = $raw -replace '#.*$', ''
        if ($line -match '^ignore:\s*$') {
            $inIgnore = $true
            continue
        }
        if (-not $inIgnore) { continue }
        # End of ignore block: any line that starts at column 0 with non-whitespace,
        # non-list-marker content (i.e. a new top-level YAML key)
        if ($line -match '^\S' -and $line -notmatch '^-') {
            $inIgnore = $false
            continue
        }
        if ($line -match '^\s*-\s+(.+?)\s*$') {
            $entry = $matches[1].Trim()
            # Strip surrounding quotes (single or double)
            $entry = $entry -replace '^["'']', '' -replace '["'']$', ''
            if ($entry) { $ignores += $entry }
        }
    }
    return $ignores
}

# Always-skip -- git metadata + the sync script itself. These are belt-and-
# suspenders against .pkgmeta drift; even if someone removes them from .pkgmeta
# they should never end up in a target install.
$AlwaysSkip = @(
    '(^|\\)\.git(\\|$)',
    '^\.gitignore$',
    '^\.gitattributes$',
    '^\.gitmodules$',
    '(^|\\)wow-version-replication\.ps1$'
)

$pkgmetaPath = Join-Path $Source ".pkgmeta"
$pkgIgnores  = Get-PkgmetaIgnores $pkgmetaPath
$pkgPatterns = $pkgIgnores | ForEach-Object { Convert-GlobToRegex $_ }

# Also treat .pkgmeta itself as ignored (it's not in its own ignore list)
$pkgPatterns += '^\.pkgmeta$'

$SkipPatterns = $AlwaysSkip + $pkgPatterns

Write-Host ""
Write-Host "=== TOGTools Dev Sync ===" -ForegroundColor Magenta
if ($DryRun) {
    Write-Host "(DRY RUN -- no files will be copied or deleted)" -ForegroundColor Yellow
}
Write-Host "Source : $Source"                  -ForegroundColor White
foreach ($d in $Destinations) {
    Write-Host "Target : $d" -ForegroundColor Green
}
if ($pkgIgnores) {
    Write-Host ("Loaded {0} ignore globs from .pkgmeta:" -f $pkgIgnores.Count) -ForegroundColor DarkGray
    foreach ($g in $pkgIgnores) {
        Write-Host "  $g" -ForegroundColor DarkGray
    }
}
Write-Host "Press Ctrl+C to stop." -ForegroundColor White
Write-Host ""

function Skip-Path([string]$rel) {
    foreach ($p in $SkipPatterns) {
        if ($rel -match $p) { return $true }
    }
    return $false
}

function Sync-File([string]$fullPath, [string]$verb) {
    $rel = $fullPath.Substring($Source.Length).TrimStart('\','/')
    if (Skip-Path $rel) {
        if ($DryRun) {
            Write-Host "[skip] $rel" -ForegroundColor DarkGray
        }
        return
    }

    $ts = Get-Date -Format "HH:mm:ss"
    $tag = if ($DryRun) { "WOULD" } else { $verb }

    foreach ($dest in $Destinations) {
        $target = Join-Path $dest $rel
        if ($verb -eq "Deleted") {
            if (Test-Path $target -ErrorAction SilentlyContinue) {
                if (-not $DryRun) {
                    Remove-Item $target -Force -Recurse -ErrorAction SilentlyContinue
                }
                Write-Host "[$ts] $($tag.PadRight(7)) DEL $rel" -ForegroundColor Red
            }
        } else {
            $dir = Split-Path $target -Parent
            if (-not (Test-Path $dir) -and -not $DryRun) {
                New-Item -ItemType Directory -Path $dir -Force | Out-Null
            }
            if (-not $DryRun) {
                Copy-Item $fullPath $target -Force
            }
            Write-Host "[$ts] $($tag.PadRight(7)) $rel" -ForegroundColor Cyan
        }
    }
}

# Full initial sync
Write-Host "Initial sync..." -ForegroundColor Yellow
Get-ChildItem -Path $Source -Recurse -File -Force | ForEach-Object { Sync-File $_.FullName "SYNC" }
Write-Host "Ready." -ForegroundColor Green
Write-Host ""

# In dry-run mode, exit after the initial sync. The whole point is "show me
# what would happen" -- there's no value in then sitting in a watcher loop.
if ($DryRun) {
    Write-Host "Dry run complete. Nothing was copied or deleted." -ForegroundColor Yellow
    exit 0
}

# Polling watcher -- scans the source tree every 2 seconds and syncs any file
# whose LastWriteTime advanced. Polling instead of Register-ObjectEvent because
# the event-action block runs in a separate runspace where this script's
# functions ($Source, $Destinations, Sync-File) are not in scope, so file
# changes were firing but silently doing nothing. Polling is slower (2 s
# latency) but reliable across PS5.1 / PS7 and across editor save patterns
# (some editors write-replace, which the FileSystemWatcher can mis-fire on).

$lastWrites = @{}
foreach ($f in Get-ChildItem -Path $Source -Recurse -File -Force) {
    $lastWrites[$f.FullName] = $f.LastWriteTimeUtc
}

Write-Host "Watching for changes (Ctrl+C to stop)..." -ForegroundColor Yellow
try {
    while ($true) {
        Start-Sleep -Seconds 2

        # Created / Changed
        foreach ($f in Get-ChildItem -Path $Source -Recurse -File -Force) {
            $cur  = $f.LastWriteTimeUtc
            $prev = $lastWrites[$f.FullName]
            if ($null -eq $prev) {
                Sync-File $f.FullName "Created"
                $lastWrites[$f.FullName] = $cur
            } elseif ($cur -gt $prev) {
                Sync-File $f.FullName "Changed"
                $lastWrites[$f.FullName] = $cur
            }
        }

        # Deleted -- any path we knew about that no longer exists on disk
        $toRemove = @()
        foreach ($k in $lastWrites.Keys) {
            if (-not (Test-Path -LiteralPath $k)) {
                Sync-File $k "Deleted"
                $toRemove += $k
            }
        }
        foreach ($k in $toRemove) { $lastWrites.Remove($k) }
    }
} finally {
    Write-Host "Watcher stopped." -ForegroundColor Yellow
}
