Private/WorkspaceHelpers.ps1

function Get-WorkspacePath {
    <#
    .SYNOPSIS
        Gets the budget workspace root path.
     
    .DESCRIPTION
        Returns the workspace root path from preferences or the default location.
        Default location is %USERPROFILE%\Documents\.mybudget
     
    .OUTPUTS
        String - Full path to workspace root
    #>

    [CmdletBinding()]
    param()
    
    $preferences = Get-Preferences -ErrorAction SilentlyContinue
    
    if ($preferences -and $preferences.workspacePath) {
        return $preferences.workspacePath
    }
    
    # Default workspace location
    $documentsPath = [Environment]::GetFolderPath('MyDocuments')
    return Join-Path $documentsPath '.mybudget'
}

function Get-BudgetPath {
    <#
    .SYNOPSIS
        Resolves a budget name to its full directory path.
     
    .DESCRIPTION
        Takes a budget name and returns the full path to its directory.
        Uses the workspace path from preferences or default location.
     
    .PARAMETER BudgetName
        The name of the budget to resolve.
     
    .PARAMETER WorkspacePath
        Optional workspace path override.
     
    .OUTPUTS
        String - Full path to budget directory
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$BudgetName,
        
        [Parameter()]
        [string]$WorkspacePath
    )
    
    if (-not $WorkspacePath) {
        $WorkspacePath = Get-WorkspacePath
    }
    
    return Join-Path (Join-Path $WorkspacePath 'budgets') $BudgetName
}

function Get-Preferences {
    <#
    .SYNOPSIS
        Loads user preferences from preferences.json.
     
    .DESCRIPTION
        Reads and deserializes the preferences.json file from the workspace root.
        Returns null if file doesn't exist.
     
    .PARAMETER WorkspacePath
        Optional workspace path override.
     
    .OUTPUTS
        PSCustomObject - Preferences object or $null if not found
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$WorkspacePath
    )
    
    if (-not $WorkspacePath) {
        $documentsPath = [Environment]::GetFolderPath('MyDocuments')
        $WorkspacePath = Join-Path $documentsPath '.mybudget'
    }
    
    $preferencesPath = Join-Path $WorkspacePath 'preferences.json'
    
    if (-not (Test-Path $preferencesPath)) {
        return $null
    }
    
    try {
        $json = Get-Content -Path $preferencesPath -Raw | ConvertFrom-Json
        return $json
    }
    catch {
        Write-Error "Failed to load preferences: $_"
        return $null
    }
}

function Set-Preferences {
    <#
    .SYNOPSIS
        Saves user preferences to preferences.json.
     
    .DESCRIPTION
        Serializes and writes the preferences object to preferences.json.
     
    .PARAMETER Preferences
        The preferences object to save.
     
    .PARAMETER WorkspacePath
        Optional workspace path override.
     
    .OUTPUTS
        Boolean - True if successful
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Preferences,
        
        [Parameter()]
        [string]$WorkspacePath
    )
    
    if (-not $WorkspacePath) {
        $WorkspacePath = $Preferences.workspacePath
        if (-not $WorkspacePath) {
            $documentsPath = [Environment]::GetFolderPath('MyDocuments')
            $WorkspacePath = Join-Path $documentsPath '.mybudget'
        }
    }
    
    $preferencesPath = Join-Path $WorkspacePath 'preferences.json'
    
    # Ensure workspace directory exists
    if (-not (Test-Path $WorkspacePath)) {
        New-Item -Path $WorkspacePath -ItemType Directory -Force | Out-Null
    }
    
    try {
        $Preferences | ConvertTo-Json -Depth 10 | Set-Content -Path $preferencesPath -Encoding UTF8
        return $true
    }
    catch {
        Write-Error "Failed to save preferences: $_"
        return $false
    }
}

function Test-WorkspaceInitialized {
    <#
    .SYNOPSIS
        Checks if the budget workspace has been initialized.
     
    .DESCRIPTION
        Verifies that the workspace directory and preferences.json exist.
     
    .PARAMETER WorkspacePath
        Optional workspace path to check.
     
    .OUTPUTS
        Boolean - True if workspace is initialized
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$WorkspacePath
    )
    
    if (-not $WorkspacePath) {
        $WorkspacePath = Get-WorkspacePath
    }
    
    $preferencesPath = Join-Path $WorkspacePath 'preferences.json'
    $budgetsPath = Join-Path $WorkspacePath 'budgets'
    
    return (Test-Path $preferencesPath) -and (Test-Path $budgetsPath)
}

function Get-BudgetMetadata {
    <#
    .SYNOPSIS
        Loads metadata for a budget.
     
    .DESCRIPTION
        Reads and deserializes the metadata.json file from a budget directory.
     
    .PARAMETER BudgetName
        The name of the budget.
     
    .PARAMETER BudgetPath
        Optional direct path to budget directory.
     
    .OUTPUTS
        PSCustomObject - Budget metadata or $null if not found
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$BudgetName,
        
        [Parameter()]
        [string]$BudgetPath
    )
    
    if (-not $BudgetPath -and $BudgetName) {
        $BudgetPath = Get-BudgetPath -BudgetName $BudgetName
    }
    
    if (-not $BudgetPath) {
        Write-Error "BudgetName or BudgetPath must be provided"
        return $null
    }
    
    $metadataPath = Join-Path $BudgetPath 'metadata.json'
    
    if (-not (Test-Path $metadataPath)) {
        return $null
    }
    
    try {
        $json = Get-Content -Path $metadataPath -Raw | ConvertFrom-Json
        return $json
    }
    catch {
        Write-Error "Failed to load budget metadata: $_"
        return $null
    }
}

function Set-BudgetMetadata {
    <#
    .SYNOPSIS
        Saves metadata for a budget.
     
    .DESCRIPTION
        Serializes and writes the metadata object to metadata.json in the budget directory.
     
    .PARAMETER Metadata
        The metadata object to save.
     
    .PARAMETER BudgetPath
        Path to the budget directory.
     
    .OUTPUTS
        Boolean - True if successful
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [PSCustomObject]$Metadata,
        
        [Parameter(Mandatory)]
        [string]$BudgetPath
    )
    
    $metadataPath = Join-Path $BudgetPath 'metadata.json'
    
    # Ensure budget directory exists
    if (-not (Test-Path $BudgetPath)) {
        New-Item -Path $BudgetPath -ItemType Directory -Force | Out-Null
    }
    
    try {
        $Metadata | ConvertTo-Json -Depth 10 | Set-Content -Path $metadataPath -Encoding UTF8
        return $true
    }
    catch {
        Write-Error "Failed to save budget metadata: $_"
        return $false
    }
}

function Get-ActiveBudgetPath {
    <#
    .SYNOPSIS
        Gets the full path to the currently active budget.
     
    .DESCRIPTION
        Returns the directory path for the active budget from preferences.
        Returns null if no workspace is initialized or no active budget is set.
     
    .OUTPUTS
        String - Full path to active budget directory or $null
    #>

    [CmdletBinding()]
    param()
    
    if (-not (Test-WorkspaceInitialized)) {
        return $null
    }
    
    $preferences = Get-Preferences
    if (-not $preferences -or -not $preferences.activeBudget) {
        return $null
    }
    
    return Get-BudgetPath -BudgetName $preferences.activeBudget -WorkspacePath $preferences.workspacePath
}

function Resolve-DataPath {
    <#
    .SYNOPSIS
        Resolves the data path for entity storage based on context.
     
    .DESCRIPTION
        Determines the appropriate data path for entity storage:
        1. If DataPath is explicitly provided, use it (legacy mode)
        2. If Budget parameter is provided, resolve to that budget's path
        3. Otherwise, use the active budget from workspace
        4. Fall back to default path if no workspace initialized
     
    .PARAMETER DataPath
        Explicit data path (legacy parameter).
     
    .PARAMETER Budget
        Budget name to target.
     
    .OUTPUTS
        String - Resolved data path
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$DataPath,
        
        [Parameter()]
        [string]$Budget
    )
    
    # Priority 1: Explicit DataPath (legacy mode)
    if ($DataPath) {
        return $DataPath
    }
    
    # Priority 2: Specific budget parameter
    if ($Budget) {
        if (-not (Test-WorkspaceInitialized)) {
            Write-Error "Budget workspace not initialized. Run Initialize-BudgetWorkspace first."
            return $null
        }
        
        $budgetPath = Get-BudgetPath -BudgetName $Budget
        if (-not (Test-Path $budgetPath)) {
            Write-Error "Budget '$Budget' not found. Use Get-Budget to see available budgets."
            return $null
        }
        
        return $budgetPath
    }
    
    # Priority 3: Active budget from workspace
    if (Test-WorkspaceInitialized) {
        $activePath = Get-ActiveBudgetPath
        if ($activePath) {
            return $activePath
        }
        
        Write-Warning "No active budget set. Use Set-ActiveBudget or provide -Budget parameter."
    }
    
    # Fall back to default path (backwards compatibility)
    return $script:DefaultDataPath
}