Goto.psm1

$Global:DirectoryAliases = @{}
$Global:DirectoryStack = @{}

function Initialize-GotoEnvironment {
    $scriptDirectory = "$HOME\PowerShellScripts"
    $scriptPath = Join-Path -Path $scriptDirectory -ChildPath "Goto.psm1"

    # Create the directory if it doesn't exist
    New-Item -ItemType Directory -Path $scriptDirectory -Force | Out-Null

    # Copy the module file to the scripts directory
    Copy-Item -Path $PSScriptRoot\Goto.psm1 -Destination $scriptPath -Force

    # Define the content to be added to the PowerShell profile
    $profileContent = @"
 
# Auto-generated import for Goto module
Import-Module "$scriptPath"
Import-Aliases
"@


    if (-Not (Select-String -Path $PROFILE -Pattern "Goto.psm1" -Quiet)) {
        Add-Content -Path $PROFILE -Value $profileContent
        Write-Host "Goto module has been added to your PowerShell profile." -ForegroundColor Green
        Write-Host "Please reload your PowerShell session or run 'Import-Aliases' to start using Goto." -ForegroundColor Yellow
    }
 else {
        Write-Host "Goto module is already registered in your PowerShell profile." -ForegroundColor Cyan
    }
}
function Invoke-VersionCheck {
    $installedModule = Get-InstalledModule -Name Goto -ErrorAction SilentlyContinue
    $onlineModule = Find-Module -Name Goto -ErrorAction SilentlyContinue

    if ($installedModule -and $onlineModule) {
        if ($onlineModule.Version -gt $installedModule.Version) {
            Write-Host "`nNew version $($onlineModule.Version) is available! Your version: $($installedModule.Version)" -ForegroundColor Cyan
            Write-Host "To update, run the following command:" -ForegroundColor Cyan
            Write-Host " goto update" -ForegroundColor Green
        }
    }
}

function Save-Aliases {
    $path = Join-Path $env:USERPROFILE "PowerShellScripts\DirectoryAliases.json"
    $Global:DirectoryAliases | ConvertTo-Json | Set-Content -Path $path
}

function Import-Aliases {
    $path = Join-Path $env:USERPROFILE "PowerShellScripts\DirectoryAliases.json"
    if (Test-Path $path) {
        try {
            $jsonContent = Get-Content -Path $path | ConvertFrom-Json
            $Global:DirectoryAliases = @{}
            foreach ($entry in $jsonContent.PSObject.Properties) {
                $Global:DirectoryAliases[$entry.Name] = $entry.Value
            }
        }
        catch {
            Write-Warning "Failed to load aliases from $path due to invalid format."
            $Global:DirectoryAliases = @{}
        }
    }
    else {
        Write-Host "No aliases file found at $path. Starting with an empty aliases list."
        $Global:DirectoryAliases = @{}
    }
}

function _goto_print_similar {
    param([string]$aliasInput)

    $normalizedInput = $aliasInput.ToLower()
    $matches = $Global:DirectoryAliases.Keys | Where-Object {
        $alias = $_.ToLower()
        $inputChars = $normalizedInput.ToCharArray()
        $aliasChars = $alias.ToCharArray()
        $matchingCharsCount = ($inputChars | Where-Object { $aliasChars -contains $_ }).Count
        $matchingCharsCount -gt 1
    }

    $matchedAliases = $matches | ForEach-Object {
        $alias = $_
        $aliasChars = $alias.ToLower().ToCharArray()
        $matchingCharsCount = ($normalizedInput.ToCharArray() | Where-Object { $aliasChars -contains $_ }).Count
        [PSCustomObject]@{
            Alias = $alias
            Path  = $Global:DirectoryAliases[$alias]
            Score = $matchingCharsCount
        }
    } | Sort-Object -Property Score -Descending

    $maxAliasLength = ($matchedAliases | Measure-Object -Property Alias -Maximum).Maximum.Length

    if ($matchedAliases.Count -eq 1) {
        $selectedAlias = $matchedAliases[0].Alias
        $path = $Global:DirectoryAliases[$selectedAlias]
        $spacesToAdd = $maxAliasLength - $selectedAlias.Length
        $spacesToAdd = [Math]::Max(0, $spacesToAdd)
        $aliasDisplay = $selectedAlias + (' ' * $spacesToAdd)
        Write-Host "`nOnly one matching alias found: '$aliasDisplay' -> '$path'. Navigating..." -ForegroundColor Green
        Set-Location $path
        return $selectedAlias
    }
    elseif ($matchedAliases.Count -gt 1) {
        Write-Host "`nDid you mean one of these? Type the number to navigate, or press ENTER to cancel:" -ForegroundColor Yellow
        $index = 1
        foreach ($alias in $matchedAliases) {
            $spacesToAdd = $maxAliasLength - $alias.Alias.Length
            $spacesToAdd = [Math]::Max(0, $spacesToAdd)  # Ensure non-negative value
            $aliasDisplay = $alias.Alias + (' ' * $spacesToAdd)
            Write-Host "[$index]: $aliasDisplay -> $($alias.Path)" -ForegroundColor Cyan
            $index++
        }

        Write-Host ""
        $choice = Read-Host "Enter your choice (1-$($matchedAliases.Count))"
        if ([string]::IsNullOrWhiteSpace($choice)) {
            Write-Host "No selection made. No action taken." -ForegroundColor Red
        }
        elseif ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le $matchedAliases.Count) {
            $selectedAlias = $matchedAliases[[int]$choice - 1].Alias
            $spacesToAdd = $maxAliasLength - $selectedAlias.Length
            $spacesToAdd = [Math]::Max(0, $spacesToAdd)
            $aliasDisplay = $selectedAlias + (' ' * $spacesToAdd)
            $path = $Global:DirectoryAliases[$selectedAlias]
            Write-Host "Navigating to '$aliasDisplay' -> '$path'." -ForegroundColor Green
            Set-Location $path
            return $selectedAlias
        }
        else {
            Write-Host "Invalid selection. No action taken." -ForegroundColor Red
        }
    }
    else {
        Write-Host "No similar aliases found for '$aliasInput'." -ForegroundColor Red
    }
    return $null
}

function goto {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Command,

        [Parameter(Mandatory = $false, Position = 1)]
        [string]$Alias,

        [Parameter(Mandatory = $false, Position = 2)]
        [string]$Path
    )

    if ($Global:DirectoryAliases.ContainsKey($Command)) {
        $path = $Global:DirectoryAliases[$Command]
        Write-Host "Navigating to alias '$Command' at path '$path'."
        Set-Location $path
    }
    else {
        switch ($Command) {
            'update' {
                try {
                    $currentVersion = (Get-InstalledModule -Name Goto).Version
                    Update-Module -Name Goto -Force -ErrorAction Stop
                    $newVersion = (Get-InstalledModule -Name Goto).Version
                    if ($newVersion -gt $currentVersion) {
                        Write-Host "Goto has been updated from version $currentVersion to $newVersion!" -ForegroundColor Green
                        Initialize-GotoEnvironment
                        Write-Host "Please restart your PowerShell session to use the new version." -ForegroundColor Yellow
                    }
                    else {
                        Write-Host "Goto is already at the latest version ($currentVersion)." -ForegroundColor Green
                    }
                }
                catch {
                    Write-Host "An error occurred while updating Goto: $_" -ForegroundColor Red
                }
            }
            'r' {
                if (-not [string]::IsNullOrWhiteSpace($Alias) -and -not [string]::IsNullOrWhiteSpace($Path)) {
                    if (Test-Path $Path) {
                        $resolvedPath = Resolve-Path -Path $Path | Select-Object -ExpandProperty Path
                        if ($Global:DirectoryAliases.ContainsKey($Alias)) {
                            $currentPath = $Global:DirectoryAliases[$Alias]
                            if ($currentPath -ne $resolvedPath) {
                                $overwriteConfirmation = Read-Host "`nAlias '$Alias' already exists for path '$currentPath'. Overwrite? [Y/N]"
                                if ($overwriteConfirmation -ne 'Y') {
                                    Write-Host "`nAlias registration cancelled." -ForegroundColor Yellow
                                    return
                                }
                            }
                        }
                        $Global:DirectoryAliases[$Alias] = $resolvedPath
                        Write-Host "`nAlias '$Alias' registered for path '$resolvedPath'." -ForegroundColor Green
                        Save-Aliases
                    }
                    else {
                        Write-Warning "`nThe specified path does not exist." -ForegroundColor Yellow
                    }
                }
                else {
                    Write-Warning "`nAlias name or path not specified. Usage: goto r <alias_name> <path>" -ForegroundColor Yellow
                }
            }
            'u' {
                if ($Global:DirectoryAliases.ContainsKey($Alias)) {
                    $aliasPath = $Global:DirectoryAliases[$Alias]
                    $confirmDeletion = Read-Host "Are you sure you want to unregister the alias '$Alias' which points to '$aliasPath'? [Y/N]"
                    if ($confirmDeletion -eq 'Y') {
                        $Global:DirectoryAliases.Remove($Alias)
                        Write-Host "`nAlias '$Alias' unregistered." -ForegroundColor Green
                        Save-Aliases
                    }
                    else {
                        Write-Host "`nAlias unregistration cancelled." -ForegroundColor Yellow
                    }
                }
                else {
                    Write-Warning "`nAlias '$Alias' does not exist." -ForegroundColor Yellow
                }
            }
            'l' {
                if ($Global:DirectoryAliases.Count -eq 0) {
                    Write-Host "`nNo aliases registered."
                }
                else {
                    $horizontalLine = ([char]0x2500).ToString() * 2
                    $maxAliasLength = ($Global:DirectoryAliases.Keys | Measure-Object -Maximum -Property Length).Maximum + 2
                    Write-Host ""
                    $Global:DirectoryAliases.GetEnumerator() | Sort-Object Name | ForEach-Object {
                        $aliasPadded = $_.Key.PadRight($maxAliasLength)
                        Write-Host " $aliasPadded" -ForegroundColor Green -NoNewline
                        Write-Host "$horizontalLine> " -NoNewline
                        Write-Host $_.Value -ForegroundColor Yellow
                    }
                }
            }
            'x' {
                if ($Global:DirectoryAliases.ContainsKey($Alias)) {
                    Write-Host "`n$Alias -> $($Global:DirectoryAliases[$Alias])" -ForegroundColor Green
                }
                else {
                    Write-Warning "`nAlias $Alias does not exist." -ForegroundColor Yellow
                }
            }
            'c' {
                $keysToRemove = $Global:DirectoryAliases.Keys | Where-Object {
                    $path = $Global:DirectoryAliases[$_]
                    -not [string]::IsNullOrWhiteSpace($path) -and -not (Test-Path $path)
                }

                if ($keysToRemove.Count -gt 0) {
                    foreach ($key in $keysToRemove) {
                        Write-Warning "`n Cleaning up alias '$key' because the path no longer exists." -ForegroundColor Yellow
                        $Global:DirectoryAliases.Remove($key)
                    }
                    Save-Aliases
                    Write-Host "`nCleanup complete. Removed aliases with non-existent paths." -ForegroundColor Green
                }
                else {
                    Write-Host "`nNo cleanup needed. All aliases point to valid paths." -ForegroundColor Yellow
                }
            }
            'p' {
                $currentPath = (Get-Location).Path
                Push-Location

                if ($Global:DirectoryAliases.ContainsKey($Alias)) {
                    $path = $Global:DirectoryAliases[$Alias]
                    Write-Host "`nPushed current directory '$currentPath' to stack and moved to alias '$Alias' at path '$path'." -ForegroundColor Green
                    Set-Location $path
                }
                else {
                    if (-not [string]::IsNullOrWhiteSpace($Alias)) {
                        $selectedAlias = _goto_print_similar -aliasInput $Alias
                        Write-Host "`nPushed current directory '$currentPath' to stack and moved to alias." -ForegroundColor Green
                    }
                    else {
                        Write-Host "`nNo alias specified. Pushed current directory '$currentPath' to stack." -ForegroundColor Yellow
                    }
                }
            }
            'o' {
                try {
                    Pop-Location
                    Write-Host "`nPopped and moved to the top location on the stack." -ForegroundColor Green
                }
                catch {
                    Write-Warning "`nDirectory stack is empty or an error occurred." -ForegroundColor Yellow
                }
            }
            default {
                $selectedAlias = _goto_print_similar -aliasInput $Command
                if ($selectedAlias) {
                    $path = $Global:DirectoryAliases[$selectedAlias]
                    Set-Location $path
                }
                else {
                    Write-Host "Usage: goto [ r <alias> <path> | u <alias> | l | x <alias> | c | p <alias> | o | <alias>]"
                }
            }
        }
    }
}


Initialize-GotoEnvironment
Invoke-VersionCheck
Export-ModuleMember -Function goto, Import-Aliases, Initialize-GotoEnvironment

Register-ArgumentCompleter -CommandName 'goto' -ScriptBlock {
    param($commandName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $commands = 'r', 'u', 'l', 'x', 'c', 'p', 'o'  # Register, Unregister, List, eXpand, Cleanup, Push, Pop
    $aliases = $Global:DirectoryAliases.Keys

    if ($wordToComplete -eq 'u' -or $wordToComplete -eq 'x') {
        $aliases | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }
    }
    else {
        $commands | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Perform $_ operation")
        }
    }
}