#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"
#This=$OOSH_DIR/ng/c2


### new.method

private.c2.get.functions() # <script> <?functionNameFilter> # lists all functions in a script without description
# functions are identified by someFunctionName( ) but mainly by the brackets ()
# they are parsed till the next curley bracket
# so that this detailed description is part of the grep
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi
  # Resolve to absolute path if not already (e.g. "state" → "/path/to/state")
  if [ ! -f "$from" ]; then
    local resolved
    resolved=$(command -v "$from" 2>/dev/null)
    [ -n "$resolved" ] && from="$resolved"
  fi

  local name=$(basename $from)

  cat "$from" \
  | line.find "^[^ ]*$1[^ ]*\(\) " \
  | line.filter "^{" \

}

c2.functions.get() # <script> <?functionNameFilter> # lists all functions in a script without description
{ private.c2.get.functions "$@"; }

private.c2.get.function.with.documentation() # <script> <?functionNameFilter> # lists all functions in a script with their documentation
# functions are identified by someFunctionName( ) but mainly by the brackets ()
# they are parsed till the next curley bracket
# so that this detailed description is part of the grep
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi

  local name=$(basename $from)

  cat "$from" \
  | line.find "^[^ ]*$1[^ ]*\(\) " "\{" \
  | line.filter "^{" \

  # | line.replace "$name\." \
  # | grep "$1"

  # | line.filter "^#" \
  # | sort \
}

c2.function.get.with.documentation() # <script> <?functionNameFilter> # lists all functions in a script with their documentation
{ private.c2.get.function.with.documentation "$@"; }


private.c2.format.functions() # <script> <?functionNameFilter> # formats function output with colors (pipe usage: c2.functions.get | c2.functions.format)
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi

  local name=$(basename $from)

  cat - \
  | line.replace "$" "${NO_COLOR}" \
  | line.replace "^#" "${GREEN}#" \
  | line.replace "$name\." "${CYAN}${name}${NO_COLOR}\." \
  | line.replace "\([^ ]\)()" "\1${NO_COLOR}" \
  | sed "s/</${YELLOW}</g" \
  | sed "s/>/>$(echo -n "${GREEN}")/g" \
  | line.replace "#" "${GREEN}#" \
  | line.filter "\.completion" \
  | line.filter "^private"
}

c2.functions.format() # <script> <?functionNameFilter> # formats function output with colors (pipe usage: c2.functions.get | c2.functions.format)
{ private.c2.format.functions "$@"; }

private.c2.get.formated.function.description() # <script> <?functionNameFilter> # gets formatted function description with colors for display
{
    local from="$1"
    if [ -n "$1" ]; then
      from="$1"
    shift
    else
      from="$This"
    fi

    local name=$(basename $from)

    local filter="$1"
    if [ -n "$1" ]; then
      filter="$1"
      shift
    fi

      private.c2.get.function.with.documentation $from ${filter} \
    | private.c2.format.functions $from ${filter} \
    | line.split "#" \
    | line.split "'" \
    | line.unquote \
    | line.filter "^[^ ]*$" \
    | line.replace "^" "${GREEN}"
}

c2.function.description.format() # <script> <?functionNameFilter> # gets formatted function description with colors for display
{ private.c2.get.formated.function.description "$@"; }

c2.function.completion() # <script> <?functionNameFilter> # returns list of method names for completion (filters out .completion and private methods)
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi

  local name=$(basename $from)

  private.c2.get.functions "$from" "$@" \
  | line.replace "\([^ ]\)().*$" "\1" \
  | line.replace "^$name\." \
  | line.replace " {" \
  | line.filter "\.completion" \
  | line.filter "^private" \
  | line.filter "^#" \
  | sort

}

c2.test.absolutePathName() # <?path> # tests this.absolutePathName and displays result in cyan
{
  this.absolutePathName "$@"
  echo "${CYAN}${RESULT}"
}

private.c2.get.function.declaration() # <script> <method> # extracts function declaration and parameters, saves to current.method.env
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi

  local name=$(basename $from)

  # BUG FIX: use class-qualified name to avoid substring matches.
  # Without "$name.", method "tree" matched otmux.client.choose.tree (longer
  # function names containing the method as suffix), and `sort` made
  # client.choose.tree come first alphabetically. METHOD_PARAMETER ended up
  # empty → no parameter completion ran for tree.
 private.c2.get.functions "$from" "${name}.$1" \
  | grep "${name}\.$1(" \
  | line.select 1 \
  | line.replace "\([^ ]\)()" "\1" \
  | line.replace " {" \
  | line.unify '#' " no name" " none" " please add a description" \
  | line.format FORMAT_PARSE_METHOD \
  | line.split "|" \
  | line.unquote \
  | line.replace "^declare -- METHOD_DESCRIPTION='\(.*\)" "declare -- METHOD_DESCRIPTION='\1'" >$CONFIG_PATH/current.method.env
  private.ensure.groupWrite "$CONFIG_PATH/current.method.env"

  {
    echo declare -- SCRIPT="$from"
    echo declare -- CLASS="$name"
  } >>$CONFIG_PATH/current.method.env

  if [ "$LOG_LEVEL" -gt "4" ]; then
    cat $CONFIG_PATH/current.method.env >>$LOG_DEVICE
  fi
}

c2.function.declaration.get() # <script> <method> # extracts function declaration and parameters, saves to current.method.env
{ private.c2.get.function.declaration "$@"; }


private.c2.get.function.parameter() # <script> <method> <?args> # extracts parameter definitions from function signature
{
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi

  local name=$(basename $from)

  private.c2.get.function.declaration "$from" "$1"
  shift
  [ -f "$CONFIG_PATH/current.method.env" ] && source $CONFIG_PATH/current.method.env

  echo $METHOD_PARAMETER \
  | line.parse.paramList.new "$@" >$CONFIG_PATH/result.env
  private.ensure.groupWrite "$CONFIG_PATH/result.env"

  # cat $CONFIG_PATH/result.env \
  # | line.count \
  # | line.format "declare -i PARAMS=%d\n" >>$CONFIG_PATH/result.env


  cat $CONFIG_PATH/result.env >>$CONFIG_PATH/current.method.env

  if [ "$LOG_LEVEL" -gt "4" ]; then
    cat $CONFIG_PATH/result.env >>$LOG_DEVICE
  fi

}

c2.function.parameter.get() # <script> <method> <?args> # extracts parameter definitions from function signature
{ private.c2.get.function.parameter "$@"; }


c2.debug.parse.param() # # internal: debug helper for parsing parameters (pipe usage)
{
    cat - \
  | line.unify ':' "parameterName" "addDefaultValue" \

}

c2.completion.enable() # <cmd> # enables c2 completion on a command (must be sourced interactively)
# ${RED}Does not work, since complete is an internal bash command
# it only works inteactively or when being ${YELLOW}sourced${RED} as a command
{
  local cmd="$1"
  if [ -z "$1" ]; then
    cmd=' '
  else
    shift
  fi
  complete  -o nospace -o bashdefault -o default -F "private.oo.completion" "$cmd"
  RETURN="$1"
}

# oo.completion.enable.completion() {
#   compgen -d $OOSH_DIR/$1
# }

c2.completion() # # returns list of oosh scripts available for completion
{
  compgen -f $OOSH_DIR/ | line.replace.sedquoted "${OOSH_DIR}/"
}

c2.completion.discover() # <numberOfWords> <currentWord> <script> <?method> <?rest> # main completion discovery engine
# Discovers available completions based on script, method, and parameter position
# Returns completion suggestions in $CONFIG_PATH/completion.result.txt
{
  # Ensure line.format FORMAT_* variables are available for method parsing
  [ -f "$CONFIG_PATH/lineFormat.env" ] && source "$CONFIG_PATH/lineFormat.env"
  [ -z "$FORMAT_PARSE_METHOD" ] && export FORMAT_PARSE_METHOD="declare -- METHOD='%s'|declare -- METHOD_PARAMETER='%s'|declare -- METHOD_DESCRIPTION='%s'\n"
  [ -z "$FORMAT_DECLARE_LOCAL" ] && export FORMAT_DECLARE_LOCAL="declare -- PARAM_%s='%s'\n"

  local argc=$#    # nuber of arguments
  #printf "\n"
  #clear -x

  local word="$1"  # completion word number (important for parameter)
  shift
  debug.log "c2 $* ${RED}$argc word:${word}"


  local cur="$1"
  shift
  if [ "$cur" = "--" ]; then
    cur=""
  fi

  local script="$1"
  if [ -n "$1" ]; then
    shift
  else
    script="$OOSH_DIR/ng/c2"
  fi

  if [ "$script" = "c2" ]; then
    debug.log "c2 $curr <?script:$1> -"

    script="$1"
    shift

    if [ "$script" = "-" ]; then
      c2.completion >$CONFIG_PATH/completion.result.txt
      private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
      debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
    else
      c2.completion | grep "^${script}" >$CONFIG_PATH/completion.result.txt
      private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
      debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
    fi


    local count=$( cat $CONFIG_PATH/completion.result.txt | line.count )
    completionResult=$(cat $CONFIG_PATH/completion.result.txt)

    info.log "got ${RED}${count}${NO_COLOR} suggestions"
    # if [ $word -le 1 ]; then
    #   completionResult=$(cat $CONFIG_PATH/completion.result.txt)
    # else
    #   return 0
    # fi
    if [ $word -le 1 ]; then
      return 0
    fi
  fi

  local method="$1"
  info.log "c2 ${YELLOW}<?script:$script> <?method:$method>${NO_COLOR} $*   ${RED}$argc $cur${NO_COLOR}"
  if [ -z "$completionResult" ]; then
    completionResult="$script"
  fi


  # if [ "$script" = "$completionResult" ]; then
  #   info.log "c2 ${YELLOW}<?script:$completionResult> === <?script:$script>${NO_COLOR} $*   ${RED}$argc $cur"
  # else
  #   info.log "c2 ${YELLOW}<?script:$completionResult> =!= <?script:$script>${NO_COLOR} $*   ${RED}$argc $cur"
  #   return 0
  # fi



  if [ -n "$1" ]; then shift ; fi

  info.log "c2 completion.discover ${YELLOW}<?script:$script> <?method:$method>${NO_COLOR} $*   ${RED}$argc $cur${NO_COLOR}"

  script=$( command -v $script )
  class=$(basename $script)

  filter=""
  if ! [ "$method" = "-" ]; then
    filter="$class.$method"
  fi

      # local cur prev opts;
    COMPREPLY=();
    # cur="${COMP_WORDS[COMP_CWORD]}";
    # prev="${COMP_WORDS[COMP_CWORD-1]}";



    info.log "finding functions in $script containing $method"

    #stat -L -c "%a %G %U" $CONFIG_PATH
    c2.function.completion $script ${filter} >$CONFIG_PATH/completion.result.txt
    private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
    debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"


    local count=$( cat $CONFIG_PATH/completion.result.txt | line.count )
    completionResult=$(cat $CONFIG_PATH/completion.result.txt)

    info.log "got ${RED}${count}${NO_COLOR} suggestions"
    if [ $count -le 1 ]; then
          console.log ""
          private.c2.get.formated.function.description $script ${filter}
    else
          private.c2.get.functions $script ${filter} \
          | private.c2.format.functions $script ${filter} \
          | line.replace "\.$method" ".${RED}${method}${NO_COLOR}"
    fi
    if [ $word -le 1 ] || [ "$method" = "-" ]; then
      create.result 0 ${count}
      return 0
    else
       info.log "$method" "$word"
    fi

    # When word > 1, check if cur matches sub-methods (e.g., "i" → "install.init")
    # before falling through to parameter completion
    if [ -n "$cur" ] && [ "$cur" != "-" ]; then
      local subMethodResult
      subMethodResult=$(echo "$completionResult" | grep "^${method}\.${cur}")
      if [ -n "$subMethodResult" ]; then
        echo "$subMethodResult" | sed "s/^${method}\.//" > $CONFIG_PATH/completion.result.txt
        private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
        create.result 0 $(echo "$subMethodResult" | wc -l)
        return 0
      fi
    fi

    # When a sub-method is already fully typed (e.g., "oo mode stage [tab]"),
    # resolve the compound method so parameter completion targets the sub-method
    if [ -n "$1" ] && [ "$1" != "-" ]; then
      local compoundMethod="${method}.$1"
      if echo "$completionResult" | grep -q "^${compoundMethod}$"; then
        method="$compoundMethod"
        filter="$class.$method"
        word=$((word - 1))
        shift
      fi
    fi

    # When word > 1 and method is an exact match but also a prefix of longer methods
    # (e.g., "rm" matches both "rm" and "rmi"), narrow to the exact match
    if [ $word -gt 1 ] && echo "$completionResult" | grep -q "^${method}$"; then
      echo "$method" > $CONFIG_PATH/completion.result.txt
      private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
      count=1
      completionResult="$method"
    fi

    if private.call.custom.completion "$cur" "$class" "$method"; then return 0 ; fi
    #private.call.custom.completion "$cur" "$script" "$method"
    # MKT: Here script is overridden if it is not "local" in
    # private.call.custom.completion

    debug.log "now checking for parameter"
    private.c2.get.function.parameter "$script" "$method" "$@"

    [ -f "$CONFIG_PATH/current.method.env" ] && source $CONFIG_PATH/current.method.env

    info.log "...: $@"
    console.log "" #just new line and color reset

    # THIS SHOULD NOT GO TO /dev/tty. Use logging instead!
    # echo "${YELLOW}$METHOD_PARAMETER${NO_COLOR}" \
    #   | line.replace "[<?]*" \
    #   | line.split ">" \
    #   | line.unquote >/dev/tty

     cat $CONFIG_PATH/current.method.env \
       | line.find "PARAM_" \
       | line.replace "declare -- PARAM_" "" \
       | line.replace "=.*$" >$CONFIG_PATH/completion.parameter.txt
    private.ensure.groupWrite "$CONFIG_PATH/completion.parameter.txt"


    parc=$(( $word - 2 ))

    #set -x
    PARAMETER_COMPLETION=( $( cat $CONFIG_PATH/completion.parameter.txt  ) )
    local currentParameter="${PARAMETER_COMPLETION[parc]}"


    # Show parameter names: current in cyan, others in yellow
    if [ -n "$METHOD_PARAMETER" ]; then
      local coloredParams="${YELLOW}${METHOD_PARAMETER}${NO_COLOR}"
      if [ -n "$currentParameter" ]; then
        coloredParams=$(echo "$coloredParams" | sed "s|${currentParameter}|${CYAN}${currentParameter}${YELLOW}|")
      fi
      console.log " $coloredParams"
    fi




    if private.call.custom.completion "$cur" "$class" "$method" "${currentParameter}"; then
      create.result 0 ${count}
      return 0
    else
      private.call.custom.completion "$cur" "$class" "parameter" "${currentParameter}"
    fi
    debug.log "comp result: $? ${count}"
    #set +x
}


private.call.custom.completion() # <cur> <class> <method> <?parameter> # calls custom completion function if it exists
# Tries: $class.$method.completion.$parameter, then $class.parameter.completion.$parameter
# Falls back to default value from function signature if no completion function exists
{
    debug.log "private.call.custom.completion $@"
    local cur="$1"
    local class="$2"
    local method="$3"
    if [ -n "$4" ]; then

      local parameter="$4"
      #local isOptional=$( echo $parameter | line.replace "OPTIONAL_" "" | line.replace "=.*$" )
      local isOptional=$( echo $parameter | line.replace "?" "OPTIONAL_" | line.replace "=.*$" )
      parameter=$( echo $parameter | line.replace "OPTIONAL_"  )
      parameter=".$parameter"
      parameterENV="PARAM_$4"
      #parameterENV="PARAM_$isOptional"
    fi
    info.log "private.call.custom.completion $parameter $parameterENV --- $@"

    # Very import to be local, else it overwrites from caller
    local script="$(command -v $class)"
    class=$(basename $script)
    if ! this.functionExists $class.start; then
      info.log "${GREEN}sourcing ${script}${NORMAL}"
      source $script completion.discover "$@"
    fi

    if this.functionExists $class.$method.completion$parameter; then
      $class.$method.completion$parameter "$cur" "$class" "$method" | grep "^$cur" >$CONFIG_PATH/completion.result.txt
      RETURN_VALUE=$?
      private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
      debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
      return $RETURN_VALUE
    fi

    if this.functionExists $class.parameter.completion$parameter; then
      $class.parameter.completion$parameter "$cur" "$class" "$method" | grep "^$cur" >$CONFIG_PATH/completion.result.txt
      RETURN_VALUE=$?
      private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
      debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
      return $RETURN_VALUE
    else
      debug.log "$class.$method.completion$parameter ${RED}does not exist${NO_COLOR}"

      [ -f "$CONFIG_PATH/current.method.env" ] && source $CONFIG_PATH/current.method.env
      if [ -n "$4" ] && [ "$4" != "none" ]; then
        debug.log "$parameterENV=${!parameterENV}"
        # Suppress the `addDefaultValue` placeholder. line.parse.param at
        # line:480 / line:499 uses the literal string "addDefaultValue" as
        # the second column of `line.unify` so parameters declared without
        # an explicit default still get a non-empty PARAM_<name>. When this
        # fallback echoes that value, bash readline auto-inserts the
        # literal "addDefaultValue" as the user's argument — observed for
        # `oo checkout <Tab>` on a tart_sequoia VM where the real
        # completion function (oo.checkout.completion.version) silently
        # returns nothing because origin is unreachable. Empty result lets
        # readline beep / show nothing instead of injecting nonsense.
        local _paramVal="${!parameterENV}"
        if [ "$_paramVal" = "addDefaultValue" ]; then
          : >$CONFIG_PATH/completion.result.txt
        else
          echo "$_paramVal" >$CONFIG_PATH/completion.result.txt
        fi
        private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
        debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
        return 0
      fi
    fi

    #important.log "overwriting completion.result.txt"
    echo ";" >$CONFIG_PATH/completion.result.txt
    private.ensure.groupWrite "$CONFIG_PATH/completion.result.txt"
    debug.log "c2:$LINENO completion.result.txt: -> `cat $CONFIG_PATH/completion.result.txt` <-"
    return 1
}

c2.files.completion() # <?prefix> <?dir> # returns file completion for prefix in directory
{
  important.log "c2.files.completion $*"
  if [ -z "$2" ]; then
    cd "$2"
  fi
  compgen -f  "$2$1"
}

c2.env() # <varName> # displays exported environment variable and its value
{
  echo "exported ENV variables: $*"
  echo "$1=${!1}"
}

c2.env.completion() # <prefix> # returns environment variable names matching prefix
{
  compgen -e  "$1"
}

c2.user() # <username> # displays username
{
  echo "$*"
}

c2.user.completion() # <prefix> # returns usernames matching prefix
{
  compgen -u  "$1"
}

c2.groups() # <groupname> # displays group name
{
  echo "$*"
}

c2.groups.completion() # <prefix> # returns group names matching prefix
{
  compgen -g  "$1"
}

c2.buildin.commands() # <command> # displays builtin command
{
  echo "$*"
}

c2.buildin.commands.completion() # <prefix> # returns builtin commands matching prefix
{
  compgen -c  "$1"
}

c2.jobs() # <job> # displays job
{
  echo "$*"
}

c2.jobs.completion() # <prefix> # returns jobs matching prefix
{
  compgen -j  "$1"
}

c2.variables() # <varName> # displays all shell variables
{
  echo "all shell variables: $*"
}

c2.variables.completion() # <prefix> # returns shell variable names matching prefix
{
  compgen -v  "$1"
}

c2.alias() # <aliasName> # displays alias
{
  echo "aliases: $*"
}

c2.alias.completion() # <prefix> # returns alias names matching prefix
{
  compgen -a  "$1"
}

c2.folders() # <path> # displays folder path
{
  echo "folders: $*"
}

c2.folders.completion() # <prefix> # returns folder paths matching prefix
{
  compgen -d  "$1"
}

c2.file.completion() # <prefix> # returns file paths matching prefix
{
  compgen -f "$1"
}

c2.parameter.completion.script() {
  c2.completion
}

c2.parameter.completion.path() {
  compgen -d "$1"
}

c2.parameter.completion.functionNameFilter() {
  :
}

c2.parameter.completion.method() {
  :
}

c2.usage() # # displays usage information for c2 completion system
{
  local this=${0##*/}
  echo "You started"
  echo "$0

  Usage:
  $this: is the next generation completion with regards to function parameter and types

  $this <command> [PRESS TAB]

  completes the command and finally executes it
  "
  #this.help
  echo "
      ----      --------------------------
  Examples
    $this v
    $This init

    $this c2      [PRESS TAB]
    $this devTool [PRESS TAB]
  "
}

c2.init() # # initializes c2 completion system
{
  console.log "c2 init"
}

c2.install.linux() # # installs bash-completion package on Linux
{
  if [ -n "$OOSH_PM" ]; then
    $SUDO $OOSH_PM bash-completion
  elif command -v apt-get >/dev/null 2>&1; then
    $SUDO apt-get -y install bash-completion
  elif command -v dnf >/dev/null 2>&1; then
    $SUDO dnf -y install bash-completion
  else
    error.log "No supported package manager found. Install bash-completion manually."
    return 1
  fi
}

c2.status() # # displays completion status (interactive shell only)
# ${RED}Does not work, since complete is an internal bash command
# it ouly works inteactively or when being ${YELLOW}sourced${RED} as a command
{
  console.log "c2 status
  please type

  complete

  into the console"
  complete # does not work...has to be in top level interactive shell
  cat /etc/profile.d/bash_completion.sh
}

c2.start() # <?args> # initializes c2 script and dispatches to requested method
{
  #echo "sourcing init"
  # Resolve symlink to get actual script location
  local scriptPath="${BASH_SOURCE[0]}"
  local scriptDir=$(cd "$(dirname "$scriptPath")" && pwd)
  scriptPath="$scriptDir/$(basename "$scriptPath")"
  if [ -L "$scriptPath" ]; then
    # readlink may return relative path — resolve from symlink's directory
    local linkTarget=$(readlink "$scriptPath")
    case "$linkTarget" in
      /*) scriptPath="$linkTarget" ;;
      *)  scriptPath="$scriptDir/$linkTarget" ;;
    esac
    scriptDir=$(cd "$(dirname "$scriptPath")" && pwd)
  fi

  source "$scriptDir/../this"
  source ~/config/user.env
  source "$scriptDir/../line"
  #c2.init


  this.start "$@"
}

c2.start "$@"
