Modules/businessdev.ALbuild.Apps/Public/Import-BcTranslationMemory.ps1

function Import-BcTranslationMemory {
    <#
    .SYNOPSIS
        Builds (and caches) a deterministic translation-memory index from prioritised reference XLIFFs.
 
    .DESCRIPTION
        Reads a prioritised, ordered list of reference XLIFF files and builds an in-memory index of
        known source->target translations, for Merge-BcTranslationMemory to apply to new units. Two keys
        per unit: a context-sensitive "GenNote|source" (e.g. "Table Vendor - Field No.|No.") and a plain
        "source" fallback. On a key collision the higher-priority reference wins (equal priority: the
        earlier reference wins), so an app's own / sibling-app terms override the Microsoft BC base.
 
        The index is cached under LOCALAPPDATA/ALbuild/tm (or ~/.local/share/ALbuild/tm) keyed by the
        reference files' paths + last-write-times + sizes, so the large BC base XLF (>100k units) is only
        re-parsed when a reference actually changed. Deterministic and offline: no network, no LLM.
 
    .PARAMETER Reference
        Ordered reference descriptors (highest priority first is NOT required - Priority decides). Each:
          [pscustomobject]@{ Files = <string[] paths or globs>; Origin = '<self|app:Name|bc-base>'; Priority = <int> }
 
    .PARAMETER ContextAware
        Also index the context-sensitive "GenNote|source" key. Default: $true.
 
    .PARAMETER CacheDir
        Override the cache directory (tests pass a temp dir).
 
    .PARAMETER NoCache
        Build fresh and do not read/write the cache.
 
    .OUTPUTS
        PSCustomObject { ContextAware, Count, Entries (hashtable key -> { Target, Origin, Priority }) }.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [object[]] $Reference,
        [bool] $ContextAware = $true,
        [string] $CacheDir,
        [switch] $NoCache
    )

    $prop = {
        param($obj, [string] $name, $default)
        if ($null -eq $obj) { return $default }
        $p = $obj.PSObject.Properties | Where-Object { $_.Name -ieq $name } | Select-Object -First 1
        if ($p -and $null -ne $p.Value) { return $p.Value }
        return $default
    }

    # Expand each descriptor's Files (paths/globs) to concrete, existing files, tagged with origin+priority.
    $refFiles = [System.Collections.Generic.List[object]]::new()
    foreach ($r in $Reference) {
        $origin = [string](& $prop $r 'Origin' 'unknown')
        $priority = [int](& $prop $r 'Priority' 0)
        foreach ($spec in @(& $prop $r 'Files' @())) {
            if ([string]::IsNullOrWhiteSpace($spec)) { continue }
            $resolved = @()
            if (Test-Path -LiteralPath $spec) { $resolved = @(Get-Item -LiteralPath $spec) }
            else { $resolved = @(Get-ChildItem -Path $spec -File -ErrorAction SilentlyContinue) }
            foreach ($f in $resolved) {
                if ($f.Name -like '*.g.xlf') { continue }
                $refFiles.Add([PSCustomObject]@{ Path = $f.FullName; Origin = $origin; Priority = $priority; Stamp = "$($f.LastWriteTimeUtc.Ticks):$($f.Length)" })
            }
        }
    }

    # Cache key from the resolved reference set (path+stamp) and the context-aware flag.
    $keyMaterial = ($refFiles | Sort-Object Path | ForEach-Object { "$($_.Path)|$($_.Stamp)|$($_.Priority)" }) -join ';'
    $keyMaterial += "|ctx=$ContextAware"
    $sha = [System.Security.Cryptography.SHA256]::Create()
    $hash = [BitConverter]::ToString($sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($keyMaterial))).Replace('-', '').Substring(0, 32).ToLowerInvariant()
    $sha.Dispose()

    if (-not $CacheDir) {
        $base = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } elseif ($env:XDG_DATA_HOME) { $env:XDG_DATA_HOME } else { Join-Path $HOME '.local/share' }
        $CacheDir = Join-Path $base 'ALbuild/tm'
    }
    $cacheFile = Join-Path $CacheDir "$hash.json"

    if (-not $NoCache -and (Test-Path -LiteralPath $cacheFile)) {
        try {
            $cached = Get-Content -LiteralPath $cacheFile -Raw -Encoding UTF8 | ConvertFrom-Json
            $entries = @{}
            foreach ($e in $cached.Entries.PSObject.Properties) {
                $entries[$e.Name] = [PSCustomObject]@{ Target = $e.Value.Target; Origin = $e.Value.Origin; Priority = [int]$e.Value.Priority }
            }
            Write-ALbuildLog -Level Verbose "Translation memory: loaded $($entries.Count) entries from cache."
            return [PSCustomObject]@{ ContextAware = [bool]$cached.ContextAware; Count = $entries.Count; Entries = $entries }
        }
        catch { Write-ALbuildLog -Level Verbose "Translation memory: cache read failed ($($_.Exception.Message)); rebuilding." }
    }

    # Build: add each reference unit under its plain (and, if enabled, context) key; higher priority wins.
    $entries = @{}
    $addKey = {
        param([string] $key, [string] $target, [string] $origin, [int] $priority)
        if ([string]::IsNullOrEmpty($key) -or [string]::IsNullOrEmpty($target)) { return }
        if ($entries.ContainsKey($key)) {
            if ($priority -le $entries[$key].Priority) { return }  # keep the higher (earlier on tie) priority
        }
        $entries[$key] = [PSCustomObject]@{ Target = $target; Origin = $origin; Priority = $priority }
    }

    foreach ($rf in $refFiles) {
        [xml] $doc = $null
        try { $doc = Get-Content -LiteralPath $rf.Path -Raw -Encoding UTF8 }
        catch { Write-ALbuildLog -Level Warning "Translation memory: cannot read '$($rf.Path)': $($_.Exception.Message)."; continue }
        foreach ($u in (Get-BcXliffUnit -Document $doc)) {
            if ([string]::IsNullOrEmpty($u.Source) -or [string]::IsNullOrEmpty($u.Target)) { continue }
            if ($u.TargetState -in @('needs-translation', 'needs-adaptation')) { continue }  # only completed translations
            if ($ContextAware -and $u.GenNote) { & $addKey "$($u.GenNote)|$($u.Source)" $u.Target $rf.Origin $rf.Priority }
            & $addKey "$($u.Source)" $u.Target $rf.Origin $rf.Priority
        }
    }

    if (-not $NoCache) {
        try {
            if (-not (Test-Path -LiteralPath $CacheDir)) { New-Item -ItemType Directory -Force -Path $CacheDir | Out-Null }
            $dump = [ordered]@{ ContextAware = $ContextAware; Entries = [ordered]@{} }
            foreach ($k in $entries.Keys) { $dump.Entries[$k] = $entries[$k] }
            ($dump | ConvertTo-Json -Depth 5 -Compress) | Set-Content -LiteralPath $cacheFile -Encoding UTF8
        }
        catch { Write-ALbuildLog -Level Verbose "Translation memory: cache write failed ($($_.Exception.Message))." }
    }

    Write-ALbuildLog -Level Verbose "Translation memory: built $($entries.Count) entries from $($refFiles.Count) reference file(s)."
    return [PSCustomObject]@{ ContextAware = [bool]$ContextAware; Count = $entries.Count; Entries = $entries }
}