ImportDotEnv.psm1

# DotEnv.psm1

# Requires -Version 5.1

using namespace System.IO
using namespace System.Management.Automation

$script:trueOriginalEnvironmentVariables = @{} # Stores { VarName = OriginalValueOrNull } - a persistent record of pre-module values
$script:previousEnvFiles = @()
$script:previousWorkingDirectory = $PWD.Path
$script:e = [char]27
$script:itemiserA = [char]0x2022
$script:itemiser = [char]0x21B3

# $DebugPreference = 'Continue'

function Get-RelativePath {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$Path,

    [Parameter(Mandatory)]
    [string]$BasePath
  )

  try {
    $absTarget = [System.IO.Path]::GetFullPath($Path)
    $absBase = [System.IO.Path]::GetFullPath($BasePath)

    if ($absTarget.Equals($absBase, [System.StringComparison]::OrdinalIgnoreCase)) {
        return "."
    }

    # Ensure BasePath for Uri ends with a directory separator.
    $uriBaseNormalized = $absBase
    if (-not $uriBaseNormalized.EndsWith([System.IO.Path]::DirectorySeparatorChar)) {
        $uriBaseNormalized += [System.IO.Path]::DirectorySeparatorChar
    }
    $baseUri = [System.Uri]::new($uriBaseNormalized)
    $targetUri = [System.Uri]::new($absTarget)

    $relativeUri = $baseUri.MakeRelativeUri($targetUri)
    $relativePath = [System.Uri]::UnescapeDataString($relativeUri.ToString())

    return $relativePath.Replace('/', [System.IO.Path]::DirectorySeparatorChar)
  }
  catch {
    Write-Warning "Get-RelativePath: Error calculating relative path for Target '$Path' from Base '$BasePath'. Error: $($_.Exception.Message). Falling back to original target path."
    return $Path
  }
}

function Get-EnvFilesUpstream {
  [CmdletBinding()]
  param([string]$Directory = ".")

  $currentDirNormalized = ""
  try {
    $currentDirNormalized = [System.IO.Path]::GetFullPath($Directory).TrimEnd([System.IO.Path]::DirectorySeparatorChar).ToLowerInvariant()
    $resolvedPath = Convert-Path -Path $Directory -ErrorAction Stop
  }
  catch {
    Write-Warning "Get-EnvFilesUpstream: Error resolving path '$Directory'. Error: $($_.Exception.Message). Defaulting to PWD."
    $resolvedPath = $PWD.Path
    $currentDirNormalized = [System.IO.Path]::GetFullPath($resolvedPath).TrimEnd([System.IO.Path]::DirectorySeparatorChar).ToLowerInvariant()
  }

  $envFiles = [System.Collections.Generic.List[string]]::new()
  $currentSearchDir = $resolvedPath

  while ($currentSearchDir) {
    $envPath = Join-Path $currentSearchDir ".env"
    if (Test-Path -LiteralPath $envPath -PathType Leaf) {
      $envFiles.Add($envPath)
    }
    $parentDir = Split-Path -Path $currentSearchDir -Parent
    if ($parentDir -eq $currentSearchDir -or [string]::IsNullOrEmpty($parentDir)) { break }
    $currentSearchDir = $parentDir
  }

  if ($envFiles.Count -gt 0) {
    $envFiles.Reverse()
  }
  return [string[]]$envFiles
}

function Format-EnvFilePath {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string]$Path,

    [Parameter(Mandatory)]
    [string]$BasePath
  )

  $relativePath = Get-RelativePath -Path $Path -BasePath $BasePath
  $corePath = Split-Path -Path $relativePath -Parent

  if (-not [string]::IsNullOrEmpty($corePath)) {
    $boldCore = "${script:e}[1m${corePath}${script:e}[22m"
    $relativePath = $relativePath.Replace($corePath, $boldCore)
  }

  return $relativePath
}

function Parse-EnvFile {
    param([string]$FilePath)
    $vars = @{}
    if (-not ([System.IO.File]::Exists($FilePath))) {
        Write-Debug "Parse-EnvFile: File '$FilePath' does not exist."
        return $vars
    }
    try {
        $lines = [System.IO.File]::ReadLines($FilePath)
    } catch {
        Write-Warning "Parse-EnvFile: Error reading file '$FilePath'. Error: $($_.Exception.Message)"
        return $vars
    }
    $lineNumber = 0
    foreach ($line in $lines) {
        $lineNumber++
        if ([string]::IsNullOrWhiteSpace($line)) { continue }
        $trimmed = $line.TrimStart()
        if ($trimmed.StartsWith('#')) { continue }
        $split = $line.Split('=', 2)
        if ($split.Count -eq 2) {
            $varName = $split[0].Trim()
            $varValue = $split[1].Trim()
            $vars[$varName] = @{ Value = $varValue; Line = $lineNumber; SourceFile = $FilePath }
        }
    }
    return $vars
}

function Format-VarHyperlink {
    param(
        [string]$VarName,
        [string]$FilePath,
        [int]$LineNumber
    )
    # Ensure FilePath is absolute for the hyperlink
    $absFilePath = try { Resolve-Path -LiteralPath $FilePath -ErrorAction Stop } catch { $FilePath }
    $fileUrl = "vscode://file/$($absFilePath):${LineNumber}"
    return "$script:e]8;;$fileUrl$script:e\$VarName$script:e]8;;$script:e\"
}

# --- Helper function to get effective environment variables from a list of .env files ---
function Get-EnvVarsFromFiles {
    param(
        [string[]]$Files,
        [string]$BasePath # BasePath is for context, not directly used in var aggregation here
    )

    if ($Files.Count -eq 0) {
        return @{}
    }

    if ($Files.Count -eq 1) {
        # Fast path for a single file. Parse-EnvFile returns the rich structure.
        return Parse-EnvFile -FilePath $Files[0]
    }

    # For multiple files, use RunspacePool for parallel parsing.
    $finalEffectiveVars = @{}
    $parsedResults = New-Object "object[]" $Files.Count # To store results in order

    # Define the script that will be run in each runspace.
    # It includes a minimal Parse-EnvFile definition to ensure it's available and self-contained.
    $scriptBlockText = @'
param([string]$PathToParse)
 
# Minimal Parse-EnvFile definition for use in isolated runspaces
function Parse-EnvFileInRunspace {
    param([string]$LocalFilePath)
    $localVars = @{} # PowerShell hashtable literal is fine here, it's a PS runspace
    # Directly use System.IO.File for existence and reading to minimize dependencies
    if (-not ([System.IO.File]::Exists($LocalFilePath))) {
        return $localVars
    }
    try {
        $fileLines = [System.IO.File]::ReadLines($LocalFilePath)
    } catch {
        # Silently return empty on read error in this isolated context
        return $localVars
    }
    $lineNum = 0
    foreach ($txtLine in $fileLines) {
        $lineNum++
        if ([string]::IsNullOrWhiteSpace($txtLine)) { continue }
        $trimmedTxtLine = $txtLine.TrimStart()
        if ($trimmedTxtLine.StartsWith('#')) { continue }
        $parts = $txtLine.Split('=', 2)
        if ($parts.Count -eq 2) {
            $name = $parts[0].Trim()
            $val = $parts[1].Trim()
            # This structure needs to match what the rest of the module expects
            $localVars[$name] = @{ Value = $val; Line = $lineNum; SourceFile = $LocalFilePath }
        }
    }
    return $localVars
}
 
Parse-EnvFileInRunspace -LocalFilePath $PathToParse
'@


    # Determine a reasonable number of runspaces. Cap at 8 to avoid excessive resource use.
    # Fix: [Math]::Min takes only two arguments. Nest calls for three values.
    $maxRunspaces = [Math]::Min(8, [Math]::Min($Files.Count, ([System.Environment]::ProcessorCount * 2)))
    $minRunspaces = 1

    $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2()
    # CreateDefault2 is generally good for providing access to common .NET types like System.IO.File

    $runspacePool = $null
    $psInstanceTrackers = [System.Collections.Generic.List[object]]::new()

    try {
        $runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces, $iss, $Host)
        $runspacePool.Open()

        for ($i = 0; $i -lt $Files.Count; $i++) {
            $fileToParse = $Files[$i]
            $ps = [PowerShell]::Create()
            $ps.RunspacePool = $runspacePool
            $null = $ps.AddScript($scriptBlockText).AddArgument($fileToParse)

            $asyncResult = $ps.BeginInvoke()
            $psInstanceTrackers.Add([PSCustomObject]@{
                PowerShell    = $ps
                AsyncResult   = $asyncResult
                OriginalIndex = $i
                FilePath      = $fileToParse # For logging/debugging
            })
        }

        # Wait for all to complete and collect results
        foreach ($tracker in $psInstanceTrackers) {
            try {
                $outputCollection = $tracker.PowerShell.EndInvoke($tracker.AsyncResult)

                if ($tracker.PowerShell.Streams.Error.Count -gt 0) {
                    foreach($err in $tracker.PowerShell.Streams.Error){
                        Write-Warning "Error parsing file '$($tracker.FilePath)' in parallel: $($err.ToString())"
                    }
                    $parsedResults[$tracker.OriginalIndex] = @{}
                } elseif ($outputCollection -ne $null -and $outputCollection.Count -eq 1) {
                    $singleOutput = $outputCollection[0]
                    if ($singleOutput -is [System.Collections.IDictionary]) { # Directly a hashtable
                        $parsedResults[$tracker.OriginalIndex] = $singleOutput
                    } elseif ($singleOutput -is [System.Management.Automation.PSObject] -and $singleOutput.BaseObject -is [System.Collections.IDictionary]) { # PSObject wrapping a hashtable
                        $parsedResults[$tracker.OriginalIndex] = $singleOutput.BaseObject
                    } else {
                        Write-Warning "Unexpected output type from parallel parsing of '$($tracker.FilePath)'. Type: $($singleOutput.GetType().FullName)"
                        $parsedResults[$tracker.OriginalIndex] = @{}
                    }
                } else {
                    Write-Warning "No output or multiple outputs from parallel parsing of '$($tracker.FilePath)'. Output count: $($outputCollection.Count)"
                    $parsedResults[$tracker.OriginalIndex] = @{}
                }
            } catch {
                 Write-Warning "Exception during EndInvoke for file '$($tracker.FilePath)': $($_.Exception.Message)"
                 $parsedResults[$tracker.OriginalIndex] = @{} # Store empty on exception
            }
        }
    }
    finally {
        foreach ($tracker in $psInstanceTrackers) {
            if ($tracker.PowerShell) {
                $tracker.PowerShell.Dispose()
            }
        }
        if ($runspacePool) {
            $runspacePool.Close()
            $runspacePool.Dispose()
        }
    }

    # Sequentially merge the parsed results to ensure correct precedence.
    foreach ($fileScopedVarsHashtable in $parsedResults) {
        if ($null -eq $fileScopedVarsHashtable) { continue } # Skip if null (e.g. error during parsing)
        foreach ($varNameKey in $fileScopedVarsHashtable.Keys) {
            $finalEffectiveVars[$varNameKey] = $fileScopedVarsHashtable[$varNameKey]
        }
    }
    return $finalEffectiveVars
}

function Import-DotEnv {
  [CmdletBinding(DefaultParameterSetName = 'Load', HelpUri = 'https://github.com/CosmicDNA/ImportDotEnv#readme')] # Added HelpUri
  param(
    [Parameter(ParameterSetName = 'Load', Position = 0, ValueFromPipelineByPropertyName = $true)]
    [string]$Path,

    [Parameter(ParameterSetName = 'Unload')]
    [switch]$Unload,

    [Parameter(ParameterSetName = 'Help')] # New parameter set for Help
    [switch]$Help,

    [Parameter(ParameterSetName = 'List')]
    [switch]$List
  )

  if ($PSCmdlet.ParameterSetName -eq 'Unload') {
    Write-Debug "MODULE Import-DotEnv: Called with -Unload switch."
    # ... (rest of Unload logic remains the same)
  }

  # --- Help Parameter Set Logic ---
  if ($PSCmdlet.ParameterSetName -eq 'Help' -or $Help) { # Check ParameterSetName or direct switch
    Write-Host @"
 
`e[1mImport-DotEnv Module Help`e[0m
 
This module allows for hierarchical loading and unloading of .env files.
It also provides integration with `Set-Location` (cd/sl) to automatically
manage environment variables as you navigate directories.
 
`e[1mUsage:`e[0m
 
  `e[1mImport-DotEnv`e[0m [-Path <string>]
    Loads .env files from the specified path (or current directory if no path given)
    and its parent directories. Variables from deeper .env files take precedence.
    Automatically unloads variables from previously loaded .env files if they are
    no longer applicable or have changed.
 
  `e[1mImport-DotEnv -Unload`e[0m
    Unloads all variables set by the module and resets its internal state.
 
  `e[1mImport-DotEnv -List`e[0m
    Lists currently active variables and the .env files defining them.
 
  `e[1mImport-DotEnv -Help`e[0m
    Displays this help message.
 
For `Set-Location` integration, use `Enable-ImportDotEnvCdIntegration` and `Disable-ImportDotEnvCdIntegration`.
"@

    return
  }

  if ($PSCmdlet.ParameterSetName -eq 'Unload') { # Moved Unload logic down to keep Help check first
    $varsFromLastLoad = Get-EnvVarsFromFiles -Files $script:previousEnvFiles -BasePath $script:previousWorkingDirectory

    if ($varsFromLastLoad.Count -gt 0) {
      Write-Host "`nUnloading active .env configuration..." -ForegroundColor Yellow
      foreach ($varName in $varsFromLastLoad.Keys) {
        if (-not $script:trueOriginalEnvironmentVariables.ContainsKey($varName)) {
            Write-Debug "MODULE Import-DotEnv (-Unload): No true original value recorded for '$varName'. Skipping restoration."
            continue
        }
        $originalValue = $script:trueOriginalEnvironmentVariables[$varName]
        if ($null -eq $originalValue) {
          Write-Debug "MODULE: Removing '$varName' (original value was null)"
          [Environment]::SetEnvironmentVariable($varName, $null, 'Process')
          Remove-Item "Env:\$varName" -Force -ErrorAction SilentlyContinue
        } else {
          [Environment]::SetEnvironmentVariable($varName, $originalValue, 'Process')
        }
        $searchUrl = "vscode://search/search?query=$([System.Uri]::EscapeDataString($varName))"
        $hyperlinkedVarName = "$script:e]8;;$searchUrl$script:e\$varName$script:e]8;;$script:e\"
        $restoredActionText = if ($null -eq $originalValue) { "Unset" } else { "Restored" }
        Write-Host " $script:itemiser $restoredActionText environment variable: " -NoNewline
        Write-Host $hyperlinkedVarName -ForegroundColor Yellow
      }
      $script:previousEnvFiles = @()
      $script:previousWorkingDirectory = "STATE_AFTER_EXPLICIT_UNLOAD"
      Write-Host "Environment restored. Module state reset." -ForegroundColor Green
    } else {
      Write-Host "No active .env configuration found by the module to unload." -ForegroundColor Magenta
    }
    return
  }

  if ($PSCmdlet.ParameterSetName -eq 'List') {
    Write-Debug "MODULE Import-DotEnv: Called with -List switch."
    if (-not $script:previousEnvFiles -or $script:previousEnvFiles.Count -eq 0 -or $script:previousWorkingDirectory -eq "STATE_AFTER_EXPLICIT_UNLOAD") {
      Write-Host "No .env configuration is currently active or managed by ImportDotEnv." -ForegroundColor Magenta
      return
    }
    $effectiveVars = Get-EnvVarsFromFiles -Files $script:previousEnvFiles -BasePath $script:previousWorkingDirectory
    $varToDefiningFilesMap = @{}
    foreach ($file in $script:previousEnvFiles) {
      if (-not (Test-Path -LiteralPath $file -PathType Leaf)) { continue }
      try {
        $lines = [System.IO.File]::ReadLines($file)
        foreach ($line in $lines) {
          if ($line -match '^[ \t]*#') { continue }
          if ($line -match '^[ \t]*$') { continue }
          if ($line -match '^([^=]+)=(.*)$') {
            $varName = $Matches[1].Trim()
            if (-not $varToDefiningFilesMap.ContainsKey($varName)) {
              $varToDefiningFilesMap[$varName] = [System.Collections.Generic.List[string]]::new()
            }
            $varToDefiningFilesMap[$varName].Add($file)
          }
        }
      } catch {
        Write-Warning "Import-DotEnv (-List): Error reading file '$file'. Skipping. Error: $($_.Exception.Message)"
      }
    }
    $outputObjects = @()
    foreach ($varNameKey in ($effectiveVars.Keys | Sort-Object)) {
      $searchUrl = "vscode://search/search?query=$([System.Uri]::EscapeDataString($varNameKey))"
      $hyperlinkedVarName = "$($script:e)]8;;$searchUrl$($script:e)\$varNameKey$($script:e)]8;;$($script:e)\"
      $definedInFilesDisplayString = ""
      if ($varToDefiningFilesMap.ContainsKey($varNameKey)) {
        $relativePaths = $varToDefiningFilesMap[$varNameKey] | ForEach-Object { " $(Get-RelativePath -Path $_ -BasePath $PWD.Path)" }
        $definedInFilesDisplayString = $relativePaths -join [System.Environment]::NewLine
      }
      $outputObjects += [PSCustomObject]@{
        Name         = $hyperlinkedVarName
        'Defined In' = $definedInFilesDisplayString
      }
    }
    if ($outputObjects.Count -gt 0) {
      $outputObjects | Format-Table -AutoSize
    } else {
      Write-Host "No effective variables found in the active configuration." -ForegroundColor Yellow
    }
    return
  }

  # --- Load Parameter Set Logic (Default) ---
  Write-Debug "MODULE Import-DotEnv: Called with Path '$Path' (Load set). Current PWD: $($PWD.Path)"
  if ($PSCmdlet.ParameterSetName -eq 'Load' -and (-not $PSBoundParameters.ContainsKey('Path'))) {
    $Path = "."
  }
  try {
    $resolvedPath = Convert-Path -Path $Path -ErrorAction Stop
  } catch {
    $resolvedPath = $PWD.Path
    Write-Debug "MODULE Import-DotEnv: Path '$Path' resolved to PWD '$resolvedPath' due to error: $($_.Exception.Message)"
  }

  $currentEnvFiles = Get-EnvFilesUpstream -Directory $resolvedPath
  Write-Debug "MODULE Import-DotEnv: Resolved path '$resolvedPath'. Found $($currentEnvFiles.Count) .env files upstream: $($currentEnvFiles -join ', ')"
  Write-Debug "MODULE Import-DotEnv: Previous files count: $($script:previousEnvFiles.Count) ('$($script:previousEnvFiles -join ', ')'). Previous PWD: '$($script:previousWorkingDirectory)'"

  $prevVars = Get-EnvVarsFromFiles -Files $script:previousEnvFiles -BasePath $script:previousWorkingDirectory
  $currVars = Get-EnvVarsFromFiles -Files $currentEnvFiles -BasePath $resolvedPath

  # --- Unload Phase: Unset variables that were in prevVars but not in currVars, or if their value changed ---
  $varsToUnsetOrRestore = @()
  foreach ($varNameKey in $prevVars.Keys) {
    if (-not $currVars.ContainsKey($varNameKey) -or $currVars[$varNameKey].Value -ne $prevVars[$varNameKey].Value) {
      $varsToUnsetOrRestore += $varNameKey
    }
  }

  if ($varsToUnsetOrRestore.Count -gt 0) {
    $varsToUnsetByFileMap = @{}
    foreach ($fileToScan in $script:previousEnvFiles) {
      if (-not (Test-Path -LiteralPath $fileToScan -PathType Leaf)) { continue }
      try {
        $lines = [System.IO.File]::ReadLines($fileToScan)
        foreach ($line in $lines) {
          if ($line -match '^[ \t]*#') { continue }
          if ($line -match '^[ \t]*$') { continue }
          if ($line -match '^([^=]+)=(.*)$') {
            $parsedVarName = $Matches[1].Trim()
            if ($varsToUnsetOrRestore -contains $parsedVarName) {
              if (-not $varsToUnsetByFileMap.ContainsKey($fileToScan)) { $varsToUnsetByFileMap[$fileToScan] = [System.Collections.Generic.List[string]]::new() }
              if (-not $varsToUnsetByFileMap[$fileToScan].Contains($parsedVarName)) {
                  $varsToUnsetByFileMap[$fileToScan].Add($parsedVarName)
              }
            }
          }
        }
      } catch {
        Write-Warning "Import-DotEnv (Unload Phase): Error reading file '$fileToScan'. Skipping. Error: $($_.Exception.Message)"
      }
    }
    $varsActuallyRestoredFromFile = $varsToUnsetByFileMap.Values | ForEach-Object { $_ } | Sort-Object -Unique
    $varsToRestoreNoFileAssociation = $varsToUnsetOrRestore | Where-Object { $varsActuallyRestoredFromFile -notcontains $_ }

    if ($varsToUnsetByFileMap.Count -gt 0) {
      foreach ($fileKey in $varsToUnsetByFileMap.Keys) {
        $varsForFile = $varsToUnsetByFileMap[$fileKey]
        if ($varsForFile.Count -eq 0) { continue }
        $formattedPath = Format-EnvFilePath -Path $fileKey -BasePath $PWD.Path
        Write-Host "$script:itemiserA Restoring .env file ${formattedPath}:" -ForegroundColor Yellow
        foreach ($varNameToRestore in $varsForFile) {
          $originalValue = $script:trueOriginalEnvironmentVariables[$varNameToRestore]
          if ($null -eq $originalValue) {
            [Environment]::SetEnvironmentVariable($varNameToRestore, $null, 'Process'); Remove-Item "Env:\$varNameToRestore" -Force -ErrorAction SilentlyContinue
          } else {
            [Environment]::SetEnvironmentVariable($varNameToRestore, $originalValue)
          }
          $searchUrl = "vscode://search/search?query=$([System.Uri]::EscapeDataString($varNameToRestore))"
          $hyperlinkedVarName = "$script:e]8;;$searchUrl$script:e\$varNameToRestore$script:e]8;;$script:e\"
          $restoredActionText = if ($null -eq $originalValue) { "Unset" } else { "Restored" }
          Write-Host " $script:itemiser $restoredActionText environment variable: " -NoNewline; Write-Host $hyperlinkedVarName -ForegroundColor Yellow
        }
      }
    }
    if ($varsToRestoreNoFileAssociation.Count -gt 0) {
      Write-Host "Restoring environment variables not associated with any .env file:" -ForegroundColor Yellow
      foreach ($varNameToRestore in $varsToRestoreNoFileAssociation) {
        $originalValue = $script:trueOriginalEnvironmentVariables[$varNameToRestore]
        if ($null -eq $originalValue) {
          [Environment]::SetEnvironmentVariable($varNameToRestore, $null, 'Process'); Remove-Item "Env:\$varNameToRestore" -Force -ErrorAction SilentlyContinue
        } else {
          [Environment]::SetEnvironmentVariable($varNameToRestore, $originalValue)
        }
        $searchUrl = "vscode://search/search?query=$([System.Uri]::EscapeDataString($varNameToRestore))"
        $hyperlinkedVarName = "$script:e]8;;$searchUrl$script:e\$varNameToRestore$script:e]8;;$script:e\"
        $restoredActionText = if ($null -eq $originalValue) { "Unset" } else { "Restored" }
        Write-Host " $script:itemiser $restoredActionText environment variable: " -NoNewline; Write-Host $hyperlinkedVarName -ForegroundColor Yellow
      }
    }
  }

  # --- Load Phase ---
  if ($currentEnvFiles.Count -gt 0) {
    foreach ($varNameKey in $currVars.Keys) {
      if (-not $script:trueOriginalEnvironmentVariables.ContainsKey($varNameKey)) {
        $currentEnvValue = [Environment]::GetEnvironmentVariable($varNameKey, 'Process')
        if (-not (Test-Path "Env:\$varNameKey")) {
          $script:trueOriginalEnvironmentVariables[$varNameKey] = $null
        } else {
          $script:trueOriginalEnvironmentVariables[$varNameKey] = $currentEnvValue
        }
      }
    }

    $varsToReportAsSetOrChanged = [System.Collections.Generic.List[hashtable]]::new()
    foreach ($varNameKey in $currVars.Keys) {
      $desiredVarInfo = $currVars[$varNameKey]
      $desiredValue = $desiredVarInfo.Value
      $currentValue = [Environment]::GetEnvironmentVariable($varNameKey, 'Process')
      if ($currentValue -ne $desiredValue) {
        [Environment]::SetEnvironmentVariable($varNameKey, $desiredValue)
      }
      $isNewToSession = (-not $prevVars.ContainsKey($varNameKey))
      $hasValueChanged = $false
      if (-not $isNewToSession -and $prevVars[$varNameKey].Value -ne $desiredValue) {
          $hasValueChanged = $true
      }
      if ($isNewToSession -or $hasValueChanged) {
        $varsToReportAsSetOrChanged.Add(@{
            Name       = $varNameKey
            Line       = $desiredVarInfo.Line
            SourceFile = $desiredVarInfo.SourceFile
        })
      }
    }

    if ($varsToReportAsSetOrChanged.Count -gt 0) {
      $groupedBySourceFile = $varsToReportAsSetOrChanged | Group-Object -Property SourceFile
      foreach ($fileGroup in $groupedBySourceFile) {
        $sourceFilePath = $fileGroup.Name
        $formattedPath = Format-EnvFilePath -Path $sourceFilePath -BasePath $resolvedPath
        Write-Host "$script:itemiserA Processing .env file ${formattedPath}:" -ForegroundColor Cyan
        foreach ($varDetail in $fileGroup.Group) {
          $hyperlink = Format-VarHyperlink -VarName $varDetail.Name -FilePath $varDetail.SourceFile -LineNumber $varDetail.Line
          Write-Host " $script:itemiser Setting environment variable: " -NoNewline
          Write-Host $hyperlink -ForegroundColor Green -NoNewline
          Write-Host " (from line $($varDetail.Line))"
        }
      }
    }
  }

  $script:previousEnvFiles = $currentEnvFiles
  $script:previousWorkingDirectory = $resolvedPath
}

# This function will be the wrapper for Set-Location
function Invoke-ImportDotEnvSetLocationWrapper {
  [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
  param(
    [Parameter(ParameterSetName = 'Path', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string]$Path,
    [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('PSPath')]
    [string]$LiteralPath,
    [Parameter()]
    [switch]$PassThru,
    [Parameter()]
    [string]$StackName
  )

  $slArgs = @{}
  if ($PSCmdlet.ParameterSetName -eq 'Path') {
    if ($PSBoundParameters.ContainsKey('Path')) { $slArgs.Path = $Path }
  } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
    $slArgs.LiteralPath = $LiteralPath
  }
  if ($PSBoundParameters.ContainsKey('PassThru')) { $slArgs.PassThru = $PassThru }
  if ($PSBoundParameters.ContainsKey('StackName')) { $slArgs.StackName = $StackName }

  $CommonParameters = @('Verbose', 'Debug', 'ErrorAction', 'ErrorVariable', 'WarningAction', 'WarningVariable',
    'OutBuffer', 'OutVariable', 'PipelineVariable', 'InformationAction', 'InformationVariable', 'WhatIf', 'Confirm')
  foreach ($commonParam in $CommonParameters) {
    if ($PSBoundParameters.ContainsKey($commonParam)) {
      $slArgs[$commonParam] = $PSBoundParameters[$commonParam]
    }
  }

  Microsoft.PowerShell.Management\Set-Location @slArgs
  Import-DotEnv -Path $PWD.Path
}

# Helper function to create the scriptblock for cd/sl wrappers
function New-SetLocationWrapperScriptBlock {
  param([string]$TargetFunctionFullName)
  return [scriptblock]::Create(@"
[CmdletBinding(DefaultParameterSetName='Path', SupportsShouldProcess=`$true, ConfirmImpact='Medium')]
param(
    [Parameter(ParameterSetName='Path', Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string]`$Path,
    [Parameter(ParameterSetName='LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('PSPath')]
    [string]`$LiteralPath,
    [Parameter()]
    [switch]`$PassThru,
    [Parameter()]
    [string]`$StackName
)
& `$TargetFunctionFullName @PSBoundParameters
"@
)
}

function Enable-ImportDotEnvCdIntegration {
  [CmdletBinding()]
  param()
  $currentModuleForEnable = $MyInvocation.MyCommand.Module
  if (-not $currentModuleForEnable) {
    Write-Error "Enable-ImportDotEnvCdIntegration: Module context not found." -ErrorAction Stop
  }
  if (-not $currentModuleForEnable.ExportedCommands.ContainsKey('Invoke-ImportDotEnvSetLocationWrapper')) {
    Write-Error "Enable-ImportDotEnvCdIntegration: Required wrapper 'Invoke-ImportDotEnvSetLocationWrapper' is not exported." -ErrorAction Stop
  }

  Write-Host "Enabling ImportDotEnv integration for 'Set-Location', 'cd', and 'sl' commands..." -ForegroundColor Yellow
  $wrapperFunctionFullName = "$($currentModuleForEnable.Name)\Invoke-ImportDotEnvSetLocationWrapper"
  $existingSetLocation = Get-Command Set-Location -ErrorAction SilentlyContinue
  if ($existingSetLocation -and $existingSetLocation.CommandType -eq [System.Management.Automation.CommandTypes]::Alias) {
    if (Get-Alias -Name Set-Location -ErrorAction SilentlyContinue) {
      Remove-Item -Path Alias:\Set-Location -Force -ErrorAction SilentlyContinue
    }
  }
  Set-Alias -Name Set-Location -Value $wrapperFunctionFullName -Scope Global -Force -Option ReadOnly,AllScope
  Import-DotEnv -Path $PWD.Path
  Write-Host "ImportDotEnv 'Set-Location', 'cd', 'sl' integration enabled!" -ForegroundColor Green
}

function Disable-ImportDotEnvCdIntegration {
  [CmdletBinding()]
  param()
  Write-Host "Disabling ImportDotEnv integration for 'Set-Location', 'cd', and 'sl'..." -ForegroundColor Yellow
  $currentModuleName = $MyInvocation.MyCommand.Module.Name
  if (-not $currentModuleName) {
    Write-Warning "Disable-ImportDotEnvCdIntegration: Could not determine module name. Assuming 'ImportDotEnv'."
    $currentModuleName = "ImportDotEnv"
  }
  $wrapperFunctionFullName = "$currentModuleName\Invoke-ImportDotEnvSetLocationWrapper"
  $proxiesRemoved = $false

  $slCmdInfo = Get-Command "Set-Location" -ErrorAction SilentlyContinue
  if ($slCmdInfo -and $slCmdInfo.CommandType -eq 'Alias' -and $slCmdInfo.Definition -eq $wrapperFunctionFullName) {
    Remove-Alias -Name "Set-Location" -Scope Global -Force -ErrorAction SilentlyContinue
    $proxiesRemoved = $true
  }

  Remove-Alias -Name "Set-Location" -Scope Global -Force -ErrorAction SilentlyContinue
  Remove-Item "Function:\Global:Set-Location" -Force -ErrorAction SilentlyContinue

  $finalSetLocation = Get-Command "Set-Location" -ErrorAction SilentlyContinue
  if ($null -eq $finalSetLocation -or $finalSetLocation.Source -ne "Microsoft.PowerShell.Management" -or $finalSetLocation.CommandType -ne 'Cmdlet') {
    Write-Warning "Disable-ImportDotEnvCdIntegration: 'Set-Location' may not be correctly restored to the original cmdlet."
  }

  if ($proxiesRemoved) {
    Write-Host "ImportDotEnv 'Set-Location' integration disabled, default command behavior restored." -ForegroundColor Magenta
  } else {
    Write-Host "ImportDotEnv 'Set-Location' integration was not active or already disabled." -ForegroundColor Magenta
  }
  Write-Host "Active .env variables (if any) remain loaded. Use 'Import-DotEnv -Unload' to unload them." -ForegroundColor Magenta
}

Export-ModuleMember -Function Import-DotEnv,
Enable-ImportDotEnvCdIntegration,
Disable-ImportDotEnvCdIntegration,
Invoke-ImportDotEnvSetLocationWrapper