Abbr.psm1

using namespace System.Management.Automation
using namespace System.Management.Automation.Language

$_ealiases = [ordered]@{}

function _lookup_ealias() {
  param([string]$Name)

  $metadata = _lookup_ealias_metadata($Name)
  if ($null -eq $metadata) {
    return $null
  }
  return $metadata.ExpandsTo
}

function _lookup_ealias_metadata() {
  param([string]$Name)
  return $_ealiases[$Name]
}

function abbr() {
  # for compat w/ fish abbr (that zsh now understands)
  # BONUS: abbr uses two args like ealias (below) in powershell, so finally I can have one style across ps1,zsh,fish for vanilla expansions!
  # PRN if it saves time make abbr into expansion only, leave ealias for composable+expansions like fish (and maybe port to zsh too)... if it doesn't matter for startup time then don't bother
  ealias $args[0] $args[1]
}

function ealias() {
  # usage:
  # ealias foo bar
  # ealias gcmsg 'git commit -m "' -NoSpaceAfter
  # ealias pyml '| yq' -Anywhere => 'kubectl get pods -o yaml pyml[EXPANDS]'
  param(
    [Parameter(Mandatory = $true)][string]$Name,
    [Parameter(Mandatory = $true)][string]$ExpandsTo,
    [Parameter(Mandatory = $false)][switch]$NoSpaceAfter = $false,
    [Parameter(Mandatory = $false)][switch]$Anywhere = $false
  )

  # *** use set-alias to see the $_cmd in MENU COMPLETION TOOL TIPS
  # also allows `gcm foo` to lookup expanding aliases
  Set-Alias $Name "$ExpandsTo" -Scope Global

  # metadata/lookup outside of set-alias objects
  $_ealiases[$Name] = @{
    ExpandsTo    = $ExpandsTo
    NoSpaceAfter = $NoSpaceAfter
    Anywhere     = $Anywhere
  }

}

function ExpandAliasBeforeCursor {
  param($key, $arg)
  # FYI this is split out for users to bind to other key(s) besides space, or to recompose with custom bindings of their own

  # Add space, then invoke replacement logic
  # b/c override spacebar handler, there won't be a space unless I add it
  # inserts at current cursor position - important to do that now b/c the cursor is where the user intended the space, whereas after modification the cursor might be elsewhere (ie after Replace below)
  [Microsoft.PowerShell.PSConsoleReadLine]::Insert(" ")
  # help for Insert overloads
  # https://docs.microsoft.com/en-us/dotnet/api/microsoft.powershell.psconsolereadline.insert

  $ast = $null
  $tokens = $null
  $errors = $null
  $cursor = $null
  [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)

  # this must handle cumulative adjustments from multiple replaces but the thing is after each space I would've already replaced previous ealiases
  # in fact if I copy/paste smth like `dcr dcr dcr` the spaces trigger on paste to expand already
  # so, I theoretically could stop after first replacement
  $startAdjustment = 0

  foreach ($token in $tokens) {
    # IIRC it was easier to expand on all tokens every time... I could revise this to take into account cursor position and only expand the last argument (before cursor) and then also support cursor in middle of command line too... but I won't add any of that until an issue arises as this has worked perfectly fine all along... could do smth like last token right before cursor position?

    $original = $token.Extent

    $metadata = _lookup_ealias_metadata($original.Text)
    if ($null -eq $metadata -or $null -eq $metadata.ExpandsTo) {
      continue
    }

    $anywhere = $metadata.Anywhere
    $is_command_position = $token -eq $tokens[0] # PRN if this has edge cases where command isn't first token, then address that once the problem arises, for now assume this works (to check $tokens[0])
    if (-not $anywhere -and -not $is_command_position) {
      # skip if not in command position and not marked anywhere
      continue
    }

    $expands_to = $metadata.ExpandsTo

    if (-not $metadata.NoSpaceAfter) {
      # add a space unless alias defined with NoSpaceAfter
      # i.e. `gcmsg` expands to `git commit -m "` w/o trailing space
      $expands_to = "$expands_to "

      # IIRC I had an open question about why I have to add space here again... but its working as is so leave it, IIAC this is b/c tokenizer strips them?
    }

    $original_length = $original.EndOffset - $original.StartOffset
    [Microsoft.PowerShell.PSConsoleReadLine]::Replace(
      $original.StartOffset + $startAdjustment,
      $original_length,
      $expands_to)

    # Our copy of tokens isn't updated, so adjust by the difference in length
    $startAdjustment += $expands_to.Length - $original_length
  }

  # PRN => if any expansions then take another pass! until no more expansions b/c then I can supported nested expansions!
  # i.e.:
  # ealias foo bar
  # ealias bar baz
  # foo => expands to `bar` => expands to `baz`
  # right now I only expand to `bar` and stop which has been sufficient for now

}

### Spacebar => triggers expansion
#
# scenarios:
# - typing `drc<SPACE>` => expands
# - completion: `gs<TAB>` => menu shows, tab through items, hit space to select (triggers expand)
# - if I hit enter to select an item, space can be used after that to expand it => PRN I could impl a handler for enter during completion but lets not complicate it
#
Set-PSReadLineKeyHandler -Key "Spacebar" `
  -BriefDescription "space expands ealiases" `
  -LongDescription "Spacebar handler to expand all ealiases in current line/buffer, primarily intended for ealias right before current cursor position" `
  -ScriptBlock ${function:ExpandAliasBeforeCursor}


### ENTER => triggers expansion
# i.e. if type `dcr<ENTER>` it expands to `docker-compose run` b/c of this
#
# enable validate handler (on enter):
Set-PSReadLineKeyHandler -Key Enter -Function ValidateAndAcceptLine
#

function ExpandAliasesCommandValidationHandler {
  param([CommandAst]$CommandAst)

  # I split out this function so end users can re-compose it with additional validation handler logic of their own

  $possibleAlias = $CommandAst.GetCommandName()
  # don't need metadata b/c NoSpaceAfter (only option) doesn't apply to this handler b/c this is after executing the command (possible alias) so line editing is done
  $expands_to = _lookup_ealias($possibleAlias)
  if ($null -eq $expands_to) {
    return
  }

  $original = $CommandAst.CommandElements[0].Extent
  [Microsoft.PowerShell.PSConsoleReadLine]::Replace(
    $original.StartOffset,
    $original.EndOffset - $original.StartOffset,
    $expands_to
  )

}

Set-PSReadLineOption -CommandValidationHandler ${function:ExpandAliasesCommandValidationHandler}

## Examples
# CommandValidationHandler: https://github.com/PowerShell/PSReadLine/issues/1643
# https://www.powershellgallery.com/packages/PSReadline/1.2/Content/SamplePSReadlineProfile.ps1