Private/Resolve-DataPath.ps1

# Copyright (c) 2026 Jeffrey Snover. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root.

# ── Data path resolution ──
# Reads .aitriad.json to determine where data lives.
# Priority: $env:AI_TRIAD_DATA_ROOT > .aitriad.json > platform default

$script:DataConfig = $null
$script:DataConfigDir = $null   # directory where .aitriad.json was found

function Get-PlatformDataDir {
    <#
    .SYNOPSIS
        Returns the platform-appropriate default data directory for AITriad.
    #>

    if ($IsWindows) {
        return Join-Path (Join-Path $env:LOCALAPPDATA 'AITriad') 'data'
    }
    elseif ($IsMacOS) {
        return Join-Path (Join-Path (Join-Path (Join-Path $HOME 'Library') 'Application Support') 'AITriad') 'data'
    }
    else {
        # Linux — XDG Base Directory spec
        $XdgData = $env:XDG_DATA_HOME
        if ([string]::IsNullOrWhiteSpace($XdgData)) {
            $XdgData = Join-Path (Join-Path $HOME '.local') 'share'
        }
        return Join-Path (Join-Path $XdgData 'aitriad') 'data'
    }
}

function Initialize-DataConfig {
    if ($null -ne $script:DataConfig) { return }

    # Try to load .aitriad.json from repo root (dev), module root (PSGallery),
    # or the code repo found by walking up from $PWD
    $CodeRoot = Get-CodeRoot
    $ConfigPaths = @(
        (Join-Path $script:RepoRoot '.aitriad.json')
        (Join-Path $script:ModuleRoot '.aitriad.json')
        (Join-Path $CodeRoot '.aitriad.json')
    )

    foreach ($ConfigPath in $ConfigPaths) {
        if (Test-Path $ConfigPath) {
            try {
                $script:DataConfig = Get-Content -Raw -Path $ConfigPath | ConvertFrom-Json
                $script:DataConfigDir = Split-Path $ConfigPath -Parent
                Write-Verbose "Data config: loaded from $ConfigPath"
                return
            }
            catch {
                Write-Warning "Failed to load .aitriad.json: $($_.Exception.Message)"
            }
        }
    }

    # Fallback defaults — use platform-specific data dir for PSGallery installs
    if ($script:IsDevInstall) { $DefaultRoot = '.' } else { $DefaultRoot = Get-PlatformDataDir }

    $script:DataConfig = [PSCustomObject]@{
        data_root     = $DefaultRoot
        taxonomy_dir  = 'taxonomy/Origin'
        sources_dir   = 'sources'
        summaries_dir = 'summaries'
        conflicts_dir = 'conflicts'
        debates_dir   = 'debates'
        queue_file    = '.summarise-queue.json'
        version_file  = 'TAXONOMY_VERSION'
    }
    Write-Verbose "Data config: using defaults (data_root=$DefaultRoot)"
}

function Get-CodeRoot {
    <#
    .SYNOPSIS
        Returns the resolved absolute path to the code repository root.
    .DESCRIPTION
        Priority: $env:AI_TRIAD_CODE_ROOT > walk up from $PWD for .aitriad.json >
        .aitriad.json code_root (relative to config location) > $script:RepoRoot.
        Needed when the module is installed outside the dev repo (e.g. PSGallery)
        but Electron apps still live in the original repo checkout.
    #>


    # 1. Env var takes highest priority
    if (-not [string]::IsNullOrWhiteSpace($env:AI_TRIAD_CODE_ROOT)) {
        $Resolved = Resolve-Path $env:AI_TRIAD_CODE_ROOT -ErrorAction SilentlyContinue
        if ($Resolved) { return $Resolved.Path }
        return $env:AI_TRIAD_CODE_ROOT
    }

    # 2. If we're in a dev install, RepoRoot is already the code repo
    if ($script:IsDevInstall) {
        return $script:RepoRoot
    }

    # 2b. Check code_root from bundled .aitriad.json (read directly to avoid circular call with Initialize-DataConfig)
    $BundledConfig = Join-Path $script:ModuleRoot '.aitriad.json'
    if (Test-Path $BundledConfig) {
        try {
            $Cfg = Get-Content -Raw -Path $BundledConfig | ConvertFrom-Json
            if ($Cfg.PSObject.Properties['code_root'] -and $Cfg.code_root -and (Test-Path $Cfg.code_root)) {
                return $Cfg.code_root
            }
        } catch { }
    }

    # 3. Walk up from $PWD looking for .aitriad.json (user is likely cd'd into repo)
    $Dir = (Get-Location).Path
    while ($Dir) {
        $Candidate = Join-Path $Dir '.aitriad.json'
        if (Test-Path $Candidate) {
            return $Dir
        }
        $Parent = Split-Path $Dir -Parent
        if ($Parent -eq $Dir) { break }
        $Dir = $Parent
    }

    # 4. Fallback
    return $script:RepoRoot
}

function Get-DataRoot {
    <#
    .SYNOPSIS
        Returns the resolved absolute path to the data root directory.
    #>

    Initialize-DataConfig

    # Env var takes highest priority
    if (-not [string]::IsNullOrWhiteSpace($env:AI_TRIAD_DATA_ROOT)) {
        $Resolved = Resolve-Path $env:AI_TRIAD_DATA_ROOT -ErrorAction SilentlyContinue
        if ($Resolved) { return $Resolved.Path }
        return $env:AI_TRIAD_DATA_ROOT
    }

    $Root = $script:DataConfig.data_root
    if ([System.IO.Path]::IsPathRooted($Root)) {
        return $Root
    }
    # Resolve relative to where .aitriad.json was found, then Get-CodeRoot fallback
    if ($script:DataConfigDir) { $Anchor = $script:DataConfigDir } else { $Anchor = Get-CodeRoot }
    return Join-Path $Anchor $Root
}

function Get-TaxonomyDir {
    <#
    .SYNOPSIS
        Returns the absolute path to the taxonomy directory (e.g. taxonomy/Origin).
    .PARAMETER ChildPath
        Optional child path to append (e.g. 'embeddings.json').
    #>

    param([string]$ChildPath)
    Initialize-DataConfig
    $Dir = $script:DataConfig.taxonomy_dir
    if ([System.IO.Path]::IsPathRooted($Dir)) { $Result = $Dir } else { $Result = Join-Path (Get-DataRoot) $Dir }
    if ($ChildPath) { return Join-Path $Result $ChildPath }
    return $Result
}

function Get-SourcesDir {
    Initialize-DataConfig
    $Dir = $script:DataConfig.sources_dir
    if ([System.IO.Path]::IsPathRooted($Dir)) { return $Dir }
    return Join-Path (Get-DataRoot) $Dir
}

function Get-SummariesDir {
    Initialize-DataConfig
    $Dir = $script:DataConfig.summaries_dir
    if ([System.IO.Path]::IsPathRooted($Dir)) { return $Dir }
    return Join-Path (Get-DataRoot) $Dir
}

function Get-ConflictsDir {
    Initialize-DataConfig
    $Dir = $script:DataConfig.conflicts_dir
    if ([System.IO.Path]::IsPathRooted($Dir)) { return $Dir }
    return Join-Path (Get-DataRoot) $Dir
}

function Get-DebatesDir {
    Initialize-DataConfig
    $Dir = $script:DataConfig.debates_dir
    if ([System.IO.Path]::IsPathRooted($Dir)) { return $Dir }
    return Join-Path (Get-DataRoot) $Dir
}

function Get-QueueFile {
    Initialize-DataConfig
    $File = $script:DataConfig.queue_file
    if ([System.IO.Path]::IsPathRooted($File)) { return $File }
    return Join-Path (Get-DataRoot) $File
}

function Get-VersionFile {
    Initialize-DataConfig
    $File = $script:DataConfig.version_file
    if ([System.IO.Path]::IsPathRooted($File)) { return $File }
    return Join-Path (Get-DataRoot) $File
}