scripts/modules/shared/path-functions.ps1

# strangeloop Setup - Shared Path Functions
# Version: 1.0.0


# Cross-platform path handling and conversion functions

function Convert-WindowsPathToWSL {
    <#
    .SYNOPSIS
        Converts a Windows path to WSL format
    
    .PARAMETER WindowsPath
        The Windows path to convert
    
    .EXAMPLE
        Convert-WindowsPathToWSL "C:\Users\john\project" # Returns: /mnt/c/users/john/project
    #>

    param(
        [Parameter(Mandatory)]
        [string]$WindowsPath
    )
    
    if ([string]::IsNullOrEmpty($WindowsPath)) {
        return $WindowsPath
    }
    
    # Handle UNC paths
    if ($WindowsPath.StartsWith('\\')) {
        Write-Warning "UNC paths are not supported in WSL conversion: $WindowsPath"
        return $WindowsPath
    }
    
    # Convert drive letter paths (C:\path -> /mnt/c/path)
    if ($WindowsPath -match '^([A-Za-z]):(.*)$') {
        $driveLetter = $Matches[1].ToLower()
        $pathPart = $Matches[2] -replace '\\', '/'
        return "/mnt/$driveLetter$pathPart"
    }
    
    # If it's already a Unix-style path, return as-is
    if ($WindowsPath.StartsWith('/') -or $WindowsPath.Contains('/home/')) {
        return $WindowsPath
    }
    
    # For relative paths, just convert backslashes to forward slashes
    return $WindowsPath -replace '\\', '/'
}

function Convert-WSLPathToWindows {
    <#
    .SYNOPSIS
        Converts a WSL path to Windows format
    
    .PARAMETER WSLPath
        The WSL path to convert
    
    .EXAMPLE
        Convert-WSLPathToWindows "/mnt/c/users/john/project" # Returns: C:\users\john\project
    #>

    param(
        [Parameter(Mandatory)]
        [string]$WSLPath
    )
    
    if ([string]::IsNullOrEmpty($WSLPath)) {
        return $WSLPath
    }
    
    # Handle /mnt/ paths
    if ($WSLPath -match '^/mnt/([a-z])/(.*)$') {
        $driveLetter = $Matches[1].ToUpper()
        $pathPart = $Matches[2] -replace '/', '\'
        return "${driveLetter}:\$pathPart"
    }
    
    # Handle /mnt/ paths without trailing content
    if ($WSLPath -match '^/mnt/([a-z])/?$') {
        $driveLetter = $Matches[1].ToUpper()
        return "${driveLetter}:\"
    }
    
    # If it's already a Windows-style path, return as-is
    if ($WSLPath -match '^[A-Za-z]:') {
        return $WSLPath
    }
    
    # For home directory paths (/home/), we can't convert without knowing the user
    if ($WSLPath.Contains('/home/')) {
        Write-Warning "Cannot convert WSL home directory path to Windows: $WSLPath"
        return $WSLPath
    }
    
    # For absolute Unix paths not under /mnt, we can't convert
    if ($WSLPath.StartsWith('/')) {
        Write-Warning "Cannot convert WSL absolute path to Windows: $WSLPath"
        return $WSLPath
    }
    
    # For relative paths, just convert forward slashes to backslashes
    return $WSLPath -replace '/', '\'
}

function Get-CrossPlatformPath {
    <#
    .SYNOPSIS
        Gets the appropriate path format for the current or specified platform
    
    .PARAMETER Path
        The path to convert
    
    .PARAMETER TargetPlatform
        The target platform: 'Windows', 'WSL', or 'Auto' (default)
    
    .EXAMPLE
        Get-CrossPlatformPath "C:\temp" -TargetPlatform WSL # Returns: /mnt/c/temp
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [ValidateSet('Windows', 'WSL', 'Auto')]
        [string]$TargetPlatform = 'Auto'
    )
    
    if ([string]::IsNullOrEmpty($Path)) {
        return $Path
    }
    
    # Auto-detect target platform if not specified
    if ($TargetPlatform -eq 'Auto') {
        # Check if we're running in WSL
        $TargetPlatform = if ($env:WSL_DISTRO_NAME -or $env:WSLENV) { 'WSL' } else { 'Windows' }
    }
    
    # Determine current path format
    $isWindowsPath = $Path -match '^[A-Za-z]:'
    $isWSLPath = $Path.StartsWith('/') -or $Path.Contains('/home/')
    
    # Convert if needed
    if ($TargetPlatform -eq 'WSL' -and $isWindowsPath) {
        return Convert-WindowsPathToWSL $Path
    } elseif ($TargetPlatform -eq 'Windows' -and $isWSLPath) {
        return Convert-WSLPathToWindows $Path
    } else {
        return $Path
    }
}

function Test-WSLPath {
    <#
    .SYNOPSIS
        Tests if a path is in WSL format
    
    .PARAMETER Path
        The path to test
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    return $Path.StartsWith('/') -or $Path.Contains('/home/')
}

function Test-WindowsPath {
    <#
    .SYNOPSIS
        Tests if a path is in Windows format
    
    .PARAMETER Path
        The path to test
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path
    )
    
    return $Path -match '^[A-Za-z]:'
}

function ConvertTo-NormalizedPath {
    <#
    .SYNOPSIS
        Normalizes a path by resolving relative components and standardizing separators
    
    .PARAMETER Path
        The path to normalize
    
    .PARAMETER Platform
        The target platform for separator normalization
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [ValidateSet('Windows', 'Unix', 'Auto')]
        [string]$Platform = 'Auto'
    )
    
    if ([string]::IsNullOrEmpty($Path)) {
        return $Path
    }
    
    # Auto-detect platform
    if ($Platform -eq 'Auto') {
        $Platform = if (Test-WindowsPath $Path) { 'Windows' } else { 'Unix' }
    }
    
    # Normalize separators
    if ($Platform -eq 'Windows') {
        $normalizedPath = $Path -replace '/', '\'
    } else {
        $normalizedPath = $Path -replace '\\', '/'
    }
    
    # Remove duplicate separators
    if ($Platform -eq 'Windows') {
        $normalizedPath = $normalizedPath -replace '\\+', '\'
    } else {
        $normalizedPath = $normalizedPath -replace '/+', '/'
    }
    
    # Remove trailing separators (except for root)
    if ($Platform -eq 'Windows') {
        $normalizedPath = $normalizedPath -replace '\\$', ''
        # Keep trailing backslash for drive roots
        if ($normalizedPath -match '^[A-Za-z]:$') {
            $normalizedPath += '\'
        }
    } else {
        # Don't remove trailing slash from root /
        if ($normalizedPath -ne '/') {
            $normalizedPath = $normalizedPath -replace '/$', ''
        }
    }
    
    return $normalizedPath
}

function Get-RelativePath {
    <#
    .SYNOPSIS
        Gets the relative path from one location to another
    
    .PARAMETER From
        The base path
    
    .PARAMETER To
        The target path
    #>

    param(
        [Parameter(Mandatory)]
        [string]$From,
        
        [Parameter(Mandatory)]
        [string]$To
    )
    
    try {
        # Try using built-in Resolve-Path if both paths exist
        if ((Test-Path $From) -and (Test-Path $To)) {
            $fromResolved = Resolve-Path $From
            $toResolved = Resolve-Path $To
            
            # Use .NET method for relative path calculation
            $uri1 = New-Object System.Uri($fromResolved.Path + [System.IO.Path]::DirectorySeparatorChar)
            $uri2 = New-Object System.Uri($toResolved.Path)
            $relativePath = $uri1.MakeRelativeUri($uri2).ToString()
            
            # Convert URI encoding back to regular characters
            $relativePath = [System.Uri]::UnescapeDataString($relativePath)
            
            # Convert forward slashes to platform-appropriate separators
            if ($env:OS -match "Windows") {
                $relativePath = $relativePath -replace '/', '\'
            }
            
            return $relativePath
        }
    } catch {
        # Fall back to manual calculation if built-in method fails
    }
    
    # Manual relative path calculation for cases where paths don't exist
    $fromParts = $From -split '[/\\]' | Where-Object { $_ -ne '' }
    $toParts = $To -split '[/\\]' | Where-Object { $_ -ne '' }
    
    # Find common prefix
    $commonLength = 0
    for ($i = 0; $i -lt [Math]::Min($fromParts.Length, $toParts.Length); $i++) {
        if ($fromParts[$i] -eq $toParts[$i]) {
            $commonLength++
        } else {
            break
        }
    }
    
    # Build relative path
    $upLevels = $fromParts.Length - $commonLength
    $downPath = $toParts[$commonLength..($toParts.Length - 1)]
    
    $relativeParts = @()
    for ($i = 0; $i -lt $upLevels; $i++) {
        $relativeParts += '..'
    }
    $relativeParts += $downPath
    
    $separator = if (Test-WindowsPath $From) { '\' } else { '/' }
    return $relativeParts -join $separator
}

function Expand-Path {
    <#
    .SYNOPSIS
        Expands environment variables and relative path components in a path
    
    .PARAMETER Path
        The path to expand
    
    .PARAMETER UseWSL
        Whether to use WSL for expansion (for WSL paths)
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [switch]$UseWSL
    )
    
    if ([string]::IsNullOrEmpty($Path)) {
        return $Path
    }
    
    # Handle WSL home directory expansion
    if ($Path.Contains('/home/') -and $UseWSL) {
        try {
            $expandedPath = wsl -- bash -c "echo `"$Path`"" 2>$null
            if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrEmpty($expandedPath)) {
                return $expandedPath.Trim()
            }
        } catch {
            Write-Warning "Failed to expand WSL path: $Path"
        }
    }
    
    # Handle Windows environment variable expansion
    if ($Path.Contains('%') -or $Path.Contains('$env:')) {
        try {
            return [System.Environment]::ExpandEnvironmentVariables($Path)
        } catch {
            Write-Warning "Failed to expand environment variables in path: $Path"
        }
    }
    
    # Try to resolve relative paths
    try {
        if (Test-Path $Path) {
            return (Resolve-Path $Path).Path
        }
    } catch {
        # If resolution fails, return the original path
    }
    
    return $Path
}

function Test-PathExists {
    <#
    .SYNOPSIS
        Tests if a path exists, with support for WSL paths
    
    .PARAMETER Path
        The path to test
    
    .PARAMETER UseWSL
        Whether to use WSL for testing (for WSL paths)
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [switch]$UseWSL
    )
    
    if ([string]::IsNullOrEmpty($Path)) {
        return $false
    }
    
    # For WSL paths, use WSL to test
    if ((Test-WSLPath $Path) -and $UseWSL) {
        try {
            wsl -- test -e "$Path" | Out-Null
            return $LASTEXITCODE -eq 0
        } catch {
            return $false
        }
    }
    
    # For Windows paths or when not using WSL
    return Test-Path $Path
}

function New-DirectoryIfNotExists {
    <#
    .SYNOPSIS
        Creates a directory if it doesn't exist, with support for WSL paths
    
    .PARAMETER Path
        The directory path to create
    
    .PARAMETER UseWSL
        Whether to use WSL for creation (for WSL paths)
    #>

    param(
        [Parameter(Mandatory)]
        [string]$Path,
        
        [switch]$UseWSL
    )
    
    if ([string]::IsNullOrEmpty($Path)) {
        return $false
    }
    
    # For WSL paths, use WSL to create
    if ((Test-WSLPath $Path) -and $UseWSL) {
        try {
            if (-not (Test-PathExists $Path -UseWSL)) {
                wsl -- mkdir -p "$Path" | Out-Null
                return $LASTEXITCODE -eq 0
            }
            return $true
        } catch {
            return $false
        }
    }
    
    # For Windows paths or when not using WSL
    try {
        if (-not (Test-Path $Path)) {
            New-Item -ItemType Directory -Path $Path -Force | Out-Null
        }
        return $true
    } catch {
        return $false
    }
}

# Note: These functions are available when this file is dot-sourced
# Available functions:
# - Convert-WindowsPathToWSL
# - Convert-WSLPathToWindows
# - Get-CrossPlatformPath
# - Test-WSLPath
# - Test-WindowsPath
# - ConvertTo-NormalizedPath
# - Get-RelativePath
# - Expand-Path
# - Test-PathExists
# - New-DirectoryIfNotExists