#!/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>"

# Ensure logging stubs exist — config may be sourced before log during bootstrap
[ "$(type -t console.log)" = "function" ] || console.log() { :; }
[ "$(type -t important.log)" = "function" ] || important.log() { :; }
[ "$(type -t warn.log)" = "function" ] || warn.log() { :; }
[ "$(type -t stop.log)" = "function" ] || stop.log() { :; }
[ "$(type -t debug.log)" = "function" ] || debug.log() { :; }
[ "$(type -t error.log)" = "function" ] || error.log() { echo "ERROR: $*" >&2; }

config.completion.discover() {
  #set -x
  #info.log "completion.discover $*"
  local command=$1
  shift
  local ac=$(command -v $command)
  case "${command}" in
    "init"|"oosh"|"this")
      command="this"
      ac=$OOSH_DIR/this
    ;;
    "oo")
      if [ -n "$1" ]; then
        #set -x
        command=$1
        shift
        ac=$(command -v $command)
        if [ -n "$ac" ]; then
          config.completion.discover $ac
          return 0
        else
          cd $OOSH_DIR
          compgen -f $command
          return 0
        fi
      fi
    ;;    
    "once.sh")
      command="once"
    ;;
    "config")
      debug.log "in config"
      ac=$OOSH_DIR/config
    ;;
    *)
      debug.log "$command not found in completion"
    ;;
  esac
  
  local method=""
  local previous=""
  local last=""
  while [ -n "$1" ]; do
   previous=$method
   if [ -z "$method" ]; then
    method=$1
   else
    method=$method.$1
   fi
   last=$1
   shift
  done

  # if [ -n "$command" ]; then
  #   if [ -x "$(command -v $command)" ]; then
  #     info.log "sourcing $command"
  #     #source "$command"
  #   else
  #     return 1
  #   fi
  # else
  #   return 1
  # fi


  if (config.check.completion $command $previous); then
      $ac $previous.completion $last $command $previous
  fi

  if (config.check.completion $command $last); then
    if [ "$ac" = "$OOSH_DIR/config" ]; then
      if (this.functionExists $command.$last.completion); then
        $command.$last.completion '' $command $previous
        return 0
      fi
      if (this.functionExists $command.$previous.completion); then
        $command.$previous.completion $last $command $previous
        return 0
      fi
    else

      $ac $last.completion '' $command $previous
      return 0
    fi
  fi
  # if (this.functionExists $command.$previous.completion); then
  #   $command.$previous.completion $last $command $previous
  #   return 0
  # fi

  if [[ $method == *[\\/\(\)]* ]]; then
    create.result 1 "$method contains special characters"
    #echo $RESULT
    return "$(result)" 
  fi

  #echo method:$method
  local find="^\(function \)*\($command\.\)\($method[^(#\"]*\)\(.*\)"

  grep "$find" $ac | sed 's/'"$find"'/\3/' | sed 's/\(.*\)\(\.completion.*\)/\1/'
  #echo "$@"
}

config.check.completion() {
  local command=$1
  local ac=$(command -v $command)
  local method="$2.completion"
  local find="^\(function \)*\($command\.\)\($method[^(#\"]*\)\(.*\)"
  if [[ $method == *[\\/\(\)]* ]]; then
    create.result 1 "$method contains special characters"
    #echo $RESULT
    return "$(result)"
  fi
  RESULT=$(grep "$find" $ac | sed 's/'"$find"'/\3/')
  if [ "$RESULT" = "$method" ]; then
    create.result 0 "$RESULT"
  else
    create.result 1 "$method not found"
  fi
  debug.log "$RESULT"
  return "$(result)"
}


config.edit.completion() {
  config.list.completion "$@"
}

config.delete.completion() {
  config.list.completion "$@"
}



config.list.completion.name() {
  #echo "$@"
  export FILTER=$1
  for file in ${CONFIG_PATH}/*.env 
    do
      echo "$(basename $file)" | line replace ".env"
    done
}

config.string.quote() { # <string> # quote the string to be used as a command line argument
  RESULT=$(printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/")
  echo $RESULT
}

config.init() # # inititalizes the $CONFIG environment if $CONFIG is empty
{
  export CONFIG_PATH=~/config
  export CONFIG_FILE=user.env

  if [ ! -d $CONFIG_PATH ]; then
    mkdir $CONFIG_PATH
  #else
  #  source $CONFIG
  fi
  touch $CONFIG_PATH/error.txt

  export CONFIG=$CONFIG_PATH/$CONFIG_FILE
  debug.log "$CONFIG"
}

# ---------------------------------------------------------------------------
# Repair primitives — bring a tampered/corrupted OOSH layout back to the
# canonical install state (matches what init/oosh produces). NOT called by
# the bashrc-startup `config.init` above; that path stays cheap.
# Reference state: ~/config and ~/oosh symlinks owned <user>:<user>;
# sharedConfig dir mode 2775 group dev; no self-referential symlinks.
# ---------------------------------------------------------------------------

config.init.shared() # # ensure sharedConfig dir is mode 2775 group dev; remove self-loops; idempotent
{
  # `user` script provides user.get, private.as.user, user.group.add — not
  # auto-loaded by `source this`. Source on demand so `./config init.*` works
  # in standalone invocations (the c2 dispatch path in interactive shells
  # already pulls it in).
  [ "$(type -t user.get)" = "function" ] || source "$OOSH_DIR/user" >/dev/null 2>&1
  local devHome
  devHome=$(user.get home developking 2>/dev/null)
  if [ -z "$devHome" ]; then
    error.log "config.init.shared: cannot resolve developking's home — run ${YELLOW}ossh install${NORMAL} first"
    return 1
  fi
  local baseDir
  baseDir=$(dirname "$devHome")
  local sharedConfig="$baseDir/shared/EAMD.ucp/Scenarios/localhost/EAM/1_infrastructure/Once.sh/sharedConfig"
  if [ ! -d "$sharedConfig" ]; then
    error.log "config.init.shared: sharedConfig missing: ${RED}$sharedConfig${NORMAL} — install not completed"
    return 1
  fi
  important.log "config.init.shared: normalising ${YELLOW}$sharedConfig${NORMAL} (mode 2775, group dev)"
  private.ensure.sharedTree "$sharedConfig"
}

config.init.user() # <username:$USER> # ensure the user's ~/config + ~/oosh symlinks match canonical install
{
  [ "$(type -t user.get)" = "function" ] || source "$OOSH_DIR/user" >/dev/null 2>&1
  local username="$1"
  if [ -n "$1" ]; then shift; fi
  if [ -z "$username" ]; then username="$USER"; fi

  local targetHome
  targetHome=$(user.get home "$username" 2>/dev/null)
  if [ -z "$targetHome" ]; then
    error.log "config.init.user: cannot resolve home for ${YELLOW}$username${NORMAL}"
    return 1
  fi
  local devHome
  devHome=$(user.get home developking 2>/dev/null)
  if [ -z "$devHome" ]; then
    error.log "config.init.user: developking missing — run ${YELLOW}ossh install${NORMAL} first"
    return 1
  fi
  local baseDir
  baseDir=$(dirname "$devHome")
  local sharedConfig="$baseDir/shared/EAMD.ucp/Scenarios/localhost/EAM/1_infrastructure/Once.sh/sharedConfig"
  # Resolve the canonical shared oosh dir. Preference order, picking the
  # first that resolves to an existing dir under the shared tree:
  #   1. The current $OOSH_DIR (we're running from it — trustworthy).
  #   2. The existing target of <user>'s ~/oosh symlink (if already aligned).
  #   3. The branch named by $OOSH_MODE / git fallback / "dev".
  # Avoids the case where $OOSH_MODE is set to a stale value like "released"
  # that doesn't have a corresponding worktree on disk.
  local sharedOoshBase="$baseDir/shared/EAMD.ucp/Components/com/ceruleanCircle/EAM/1_infrastructure/Once.sh"
  local sharedOosh=""
  if [ -d "$OOSH_DIR" ] && [ "${OOSH_DIR#$sharedOoshBase/}" != "$OOSH_DIR" ]; then
    sharedOosh="$OOSH_DIR"
  elif [ -L "$targetHome/oosh" ]; then
    local _existing
    _existing=$(readlink "$targetHome/oosh")
    if [ -d "$_existing" ] && [ "${_existing#$sharedOoshBase/}" != "$_existing" ]; then
      sharedOosh="$_existing"
    fi
  fi
  if [ -z "$sharedOosh" ]; then
    local branch="$OOSH_MODE"
    [ -z "$branch" ] && branch=$(this.git.branch.short "$OOSH_DIR" 2>/dev/null)
    [ -z "$branch" ] && branch="dev"
    sharedOosh="$sharedOoshBase/$branch"
  fi
  if [ ! -d "$sharedOosh" ]; then
    error.log "config.init.user: ${RED}$sharedOosh${NORMAL} missing — install not completed"
    return 1
  fi

  important.log "config.init.user: aligning ${YELLOW}$username${NORMAL}'s ~/config and ~/oosh to canonical state"

  # Symlink reconciliation in two phases.
  #
  # Phase 1 — recreate (only if needed): dispatch through private.as.user so
  # newly-created symlinks inherit <user>:<user> ownership naturally. This is
  # the same trick user.oosh.install uses at user:787-794 — and the reason
  # fresh installs produce hannesn:hannesn while a tampered install path can
  # leave hannesn:dev. Skip the dispatch entirely when both symlinks already
  # point at the canonical targets — avoids the runuser PAM-session overhead
  # and an observed silent-failure mode under sudo when env is sanitised.
  #
  # Phase 2 — force ownership: chown -h is run by THIS shell (not via
  # private.as.user). When the caller is root (the typical sudo path) chown
  # works for any target. When the caller is the target user, chown to their
  # own primary group works without sudo. Other combinations are skipped
  # quietly (e.g. sudoer alice repairing bob — the recreate path already
  # gave bob:bob).
  local _ts
  _ts=$(date +%Y%m%d-%H%M%S)
  local _needsRecreate=0
  if [ ! -L "$targetHome/config" ] || [ "$(readlink "$targetHome/config")" != "$sharedConfig" ] \
  || [ ! -L "$targetHome/oosh"   ] || [ "$(readlink "$targetHome/oosh")"   != "$sharedOosh"   ] \
  || [ -d "$targetHome/config" -a ! -L "$targetHome/config" ] \
  || [ -d "$targetHome/oosh"   -a ! -L "$targetHome/oosh"   ]; then
    _needsRecreate=1
  fi
  if [ "$_needsRecreate" = "1" ]; then
    # Delegate symlink convergence to the shared primitive
    # private.this.symlink.with.backup (this:171). private.as.user runs
    # the operations AS the target user, so the symlinks naturally inherit
    # <user>:<user> ownership — the canonical install layout.
    #
    # The bash -c subshell sources $OOSH_DIR/this to bring the primitive
    # (and its create.result / error.log / result dependencies) into
    # scope. We deliberately DON'T `set -e` here: `this` returns non-zero
    # at load-time as part of its dispatch protocol (it's a callable
    # script as well as a sourceable library), so `set -e` + source would
    # exit the subshell before the primitive ever ran. Explicit
    # `|| exit 1` after each primitive call gives the same fail-fast
    # behaviour without the false-positive on the source.
    private.as.user "$username" bash -c "
      cd \"$targetHome\" || exit 1
      source \"$OOSH_DIR/this\" >/dev/null 2>&1
      private.this.symlink.with.backup config '$sharedConfig' '$_ts' || exit 1
      private.this.symlink.with.backup oosh   '$sharedOosh'   '$_ts' || exit 1
    " || {
      error.log "config.init.user: failed to create symlinks for ${RED}$username${NORMAL}"
      return 1
    }
  fi
  # Phase 2 — force canonical owner on the symlinks. Only when this shell has
  # the privilege to do so (root, or self).
  if [ "$(id -u)" = "0" ] || [ "$username" = "$USER" ]; then
    chown -h "$username:$username" "$targetHome/config" "$targetHome/oosh" 2>/dev/null || true
  fi

  # bashrc — install template if the OOSH section is missing (idempotent).
  # Same one-shot .bashrc.pre-oosh backup convention as user:850.
  private.as.user "$username" bash -c "
    [ -f \"$targetHome/.bashrc\" ] && [ ! -f \"$targetHome/.bashrc.pre-oosh\" ] && cp \"$targetHome/.bashrc\" \"$targetHome/.bashrc.pre-oosh\"
    if ! grep -q 'config/user.env' \"$targetHome/.bashrc\" 2>/dev/null; then
      if [ -f \"$sharedOosh/templates/user/bashrcTemplate\" ]; then
        cp \"$sharedOosh/templates/user/bashrcTemplate\" \"$targetHome/.bashrc\"
      else
        printf '\n# oosh environment\n[ -f ~/config/user.env ] && source ~/config/user.env\n' >> \"$targetHome/.bashrc\"
      fi
    fi
    [ ! -f \"$targetHome/.bash_profile\" ] && echo '[ -f ~/.bashrc ] && source ~/.bashrc' > \"$targetHome/.bash_profile\"
  " 2>/dev/null

  # Ensure dev group membership (idempotent; user.group.add no-ops if already a member).
  if private.user.check.group dev >/dev/null 2>&1; then
    user.group.add dev "$username" >/dev/null 2>&1
  fi

  success.log "config.init.user: ${GREEN}$username${NORMAL} aligned"
}

config.init.check() # <username:$USER> # diagnostics: report symlink state, dev-group health (no fixes)
{
  [ "$(type -t user.get)" = "function" ] || source "$OOSH_DIR/user" >/dev/null 2>&1
  local username="$1"
  if [ -n "$1" ]; then shift; fi
  if [ -z "$username" ]; then username="$USER"; fi

  local targetHome
  targetHome=$(user.get home "$username" 2>/dev/null)
  if [ -z "$targetHome" ]; then
    error.log "config.init.check: cannot resolve home for ${YELLOW}$username${NORMAL}"
    return 0  # diagnostic — never block
  fi

  important.log "config.init.check: ${YELLOW}$username${NORMAL} (home: $targetHome)"

  # ~/config — owner, target, target's mode/group
  if [ -L "$targetHome/config" ]; then
    local cOwner cTarget tMode tGroup
    cOwner=$(stat -c "%U:%G" "$targetHome/config" 2>/dev/null || stat -f "%Su:%Sg" "$targetHome/config" 2>/dev/null)
    cTarget=$(readlink "$targetHome/config")
    info.log "  ~/config: owner=$cOwner target=$cTarget"
    if [ -d "$cTarget" ]; then
      tMode=$(stat -c "%a" "$cTarget" 2>/dev/null || stat -f "%Lp" "$cTarget" 2>/dev/null)
      tGroup=$(stat -c "%G" "$cTarget" 2>/dev/null || stat -f "%Sg" "$cTarget" 2>/dev/null)
      info.log "  sharedConfig: mode=$tMode group=$tGroup"
      if [ "$tGroup" != "dev" ]; then
        warn.log "  sharedConfig group ${RED}$tGroup${NORMAL} drifted from canonical ${GREEN}dev${NORMAL} — run ${YELLOW}config init.shared${NORMAL}"
      fi
      if [ -L "$cTarget/sharedConfig" ]; then
        warn.log "  self-referential symlink present at $cTarget/sharedConfig — run ${YELLOW}config init.shared${NORMAL}"
      fi
    fi
    # Symlink owner should be <user>:<user>, not <user>:dev
    case "$cOwner" in
      "$username:$username") : ;;
      *) warn.log "  ~/config owner ${RED}$cOwner${NORMAL} drifted from canonical ${GREEN}$username:$username${NORMAL} — run ${YELLOW}config init.user${NORMAL}" ;;
    esac
  else
    warn.log "  ~/config is not a symlink — run ${YELLOW}config init.user${NORMAL}"
  fi

  # Stale-dev-group warning: in /etc/group but not in active group set.
  # We can only check the *running* shell's group set when the username is
  # the caller; for other users we just check static membership.
  if getent group dev 2>/dev/null | grep -qE "(:|,)$username(,|$)"; then
    if [ "$username" = "$USER" ]; then
      if ! id -nG | grep -qw dev; then
        important.log "${YELLOW}$username${NORMAL} is in group ${GREEN}dev${NORMAL} per /etc/group, but the current shell's active groups don't include it. Log out of the desktop session ${RED}fully${NORMAL} (not just the terminal) and log back in — the active group set is captured at login time. Until then, writes to ~/config/* will hit Permission denied."
      fi
    fi
  else
    warn.log "  ${YELLOW}$username${NORMAL} not in group dev — run ${YELLOW}config init.user${NORMAL} to add"
  fi
  return 0
}

config.init.env() # # regenerate user.env + oosh.env + log.env via config.save (mirrors install at oo:1456)
{
  if [ -z "$OOSH_DIR" ]; then
    error.log "config.init.env: \$OOSH_DIR is empty — re-source ${YELLOW}this${NORMAL} or rerun under ${YELLOW}sudo -E${NORMAL}"
    return 1
  fi
  # CONFIG_PATH must point at the shared sharedConfig dir we want to write
  # into. Trust the calling context: bashrc/this set it to ~/config (the
  # symlink), which `config init.user` ensures points at sharedConfig.
  if [ -z "$CONFIG_PATH" ] || [ ! -d "$CONFIG_PATH" ]; then
    error.log "config.init.env: \$CONFIG_PATH ${RED}$CONFIG_PATH${NORMAL} missing — run ${YELLOW}config init.user${NORMAL} first"
    return 1
  fi
  # Backup user.env before regen — `config save` (no args) only re-emits
  # CONFIG_*-prefixed exports plus the static blocks at config:540-541.
  # Hand-customisations (custom non-CONFIG_* exports, manually-added source
  # lines beyond what `config add` writes) would otherwise be lost silently.
  if [ -f "$CONFIG_PATH/user.env" ]; then
    local _ts
    _ts=$(date +%Y%m%d-%H%M%S)
    cp -p "$CONFIG_PATH/user.env" "$CONFIG_PATH/user.env.bak.$_ts" 2>/dev/null \
      && important.log "config.init.env: backed up ${YELLOW}user.env${NORMAL} → ${GREEN}user.env.bak.$_ts${NORMAL}" \
      || warn.log "config.init.env: failed to back up user.env (continuing)"
  fi
  important.log "config.init.env: regenerating ${YELLOW}user.env${NORMAL} + ${YELLOW}oosh.env${NORMAL} + ${YELLOW}log.env${NORMAL} via ${YELLOW}config save${NORMAL} (mirrors install)"
  # `config save` (no args) cascades internally: writes user.env, then calls
  # itself with (oosh OOSH) and (log LOG) to populate the other two with
  # the canonical self-anchors and PATH/log-init blocks (config:543-599).
  config.save || warn.log "config.init.env: config.save returned non-zero — env files may be partial"
}

config.init.full() # <username:$USER> # repair end-to-end: shared dir + user symlinks + env files + diagnostics
{
  local username="$1"
  if [ -n "$1" ]; then shift; fi
  if [ -z "$username" ]; then username="$USER"; fi

  local rc=0
  config.init.shared || rc=$?
  config.init.user "$username" || rc=$?
  # init.env runs AFTER init.user so the ~/config symlink is canonical
  # before we write into the target. Only run for self-repair — regenerating
  # other users' env files from the caller's shell would write THIS shell's
  # OOSH_*/LOG_* state into THEIR config (likely wrong values).
  if [ "$username" = "$USER" ] || [ "$(id -u)" = "0" -a "$username" = "root" ]; then
    config.init.env || rc=$?
  fi
  config.init.check "$username" || rc=$?
  return $rc
}

config.init.shared.completion() { :; }
config.init.env.completion()    { :; }
config.init.user.completion.username()  { user.list; }
config.init.check.completion.username() { user.list; }
config.init.full.completion.username()  { user.list; }

config.usage()
{
  local this=${0##*/}
  echo "You started" 
  echo "$0

  current file: $CONFIG
   CONFIG_PATH=$CONFIG_PATH
   CONFIG_FILE=$CONFIG_FILE


  Usage:
  $this: command   description and Parameter
      v         print version information
      init      initializes a new user config
      save      <?name> <?ENV_NAME> saves all vaiables containing ENV_NAME to name.env
      list      <?name> lists all vaiables in name.env
      add       <?name> lists all vaiables containing NAME to $CONFIG
      delete    <?name> deletes name.env
  
  Examples
    update current shell
      . \$CONFIG
      . $CONFIG

    $this v
    $this save once
    $this list oosh
  "
  
  #loop.list PATH print '---------------------: '
}

config.save() # <?name> <?ENV_PREFIX> # creates an "<name>.env" config file in $CONFIG_PATH containing all env variables that match <ENV_PREFIX>
# without parameters it inititlises the $CONFIG_PATH/user.env
{
  #set -x
  console.log "config.save (CONFIG=$CONFIG)"

  local file=$1
  shift
  local name=$1
  if [ -n "$file" ] &&  [ -z "$name" ]; then
    #set -x
    warn.log "BASH 5 support only" # on bash 3 this would help: $(tr '[:lower:]' '[:upper:]' <<< "$file")
    name=${file^^}
  else
    shift
  fi


  if [ -n "$file" ]; then
    config.file $file.env
  else
    name="CONFIG"
  fi
  
  stop.log "
  config.save [file:$file] [name:$name] to $CONFIG"
    #declare -px | grep "^\(declare .* \)*\(.*$name\)\(.*\)=" | sed 's/^\(declare .* \)*\(.*'$name'\)\(.*\)=\(\"\)*\([^\"]*\)\(\"\)*/export \2\3=\"\5\"/'
    #declare -p | grep "^\(declare -.* \)*\(.*$name\)\(.*\)=" | sed 's/^\(declare -.* \)*\(.*'$name'\)\(.*\)=\(.*\)/export \2\3=\4/'
  RESULT=""
  # declare -p 
  # important.log "now filter"
  # declare -p | grep "^\(declare -[^aA]* \)*\([^ ]*$name\)\(.*\)=" 
  # important.log "now replace"
  # declare -p | grep "^\(declare -[^aA]* \)*\([^ ]*$name\)\(.*\)=" | sed 's/\([^-]*\)\(-x *\)\(.*'$name'\)\(.*\)=\(.*\)/export \1\3\4=\5/'
  {    
    #declare -p | grep "^\(declare -[^a]* \)*\([^ ]*$name\)\(.*\)=" | sed 's/^\(declare -.* \)*\((.*'$name'\)\(.*\)=\(.*\)/export \2\3=\4/'
    #declare -p | grep "^\(declare -[^aA]* \)*\([^ ]*$name\)\(.*\)=" | sed 's/^\(declare -.* \)\(.*'$name'\)\(.*\)=\(.*\)/export \2\3=\4/'
    
    # on mac without -x
    # Match vars where name is a PREFIX (e.g. OOSH matches OOSH_DIR but not SOME_OOSH_VAR)
    # Exclude install-only vars (LOG_INSTALL, INSTALL_LOG) — they must not persist into user sessions.
    # Exclude derived per-user paths — these are dynamically rebuilt from $HOME at every
    # shell init (this:209 sets CONFIG_PATH; this:40-49 sets OOSH_DIR), so persisting their
    # ABSOLUTE expanded value into shared config (the saving user's $HOME) only causes
    # EACCES leaks when a different user later sources it. Same reasoning as LOG_LIVE
    # below — log:22 already re-anchors LOG_LIVE for the current user at bashrc time;
    # OOSH_DIR is re-anchored by this; CONFIG_PATH/CONFIG by config.init (config:173).
    # OOSH_COMPONENTS_DIR is a transient /tmp/test.oo.* path from test runs — never share.
    declare -p \
      | grep " ${name}[_=]" \
      | grep -v "^declare -[aA]" \
      | grep -v 'INSTALL' \
      | grep -v 'LOG_LIVE=' \
      | grep -v 'LOG_DEVICE=' \
      | grep -v 'CONFIG_PATH=' \
      | grep -v 'CONFIG=' \
      | grep -v 'OOSH_DIR=' \
      | grep -v 'OOSH_COMPONENTS_DIR=' \
      | sed 's/\([^-]*\)\(-x *\)\(.*'$name'\)\(.*\)=\(.*\)/export \3\4=\5/'
  } >$CONFIG
  private.ensure.groupWrite "$CONFIG"

  if [ -z "$file" ]; then
    #problem.log "regenerate config"

    important.log "generating $CONFIG"
    # Self-anchor CONFIG_PATH from the file's own location so user.env
    # is sourceable in any context, including non-interactive shells
    # (GHA steps, ssh exec) that don't go through bashrc and therefore
    # never invoked `this` to set CONFIG_PATH=$HOME/config first. The
    # source lines appended by config.add (config:399) reference
    # $CONFIG_PATH and would otherwise expand to /log.env on a fresh
    # process. The `:` no-op default leaves an already-set CONFIG_PATH
    # untouched, so interactive sessions are unaffected.
    {
      # 1st line: parameter-expansion default (BASH_SOURCE-based). Wins for
      # the normal `source ~/config/user.env` case where BASH_SOURCE[0]
      # is the absolute path and `%/*` strips the trailing /user.env.
      # 2nd line: fallback to $HOME/config (the canonical install location)
      # whenever line 1 leaves CONFIG_PATH pointing somewhere that is NOT the
      # config dir — otherwise the `source $CONFIG_PATH/oosh.env` lines below
      # break. Use a POSITIVE validity test ("does $CONFIG_PATH actually hold
      # this user.env?") rather than enumerating the specific BASH_SOURCE
      # breakage shapes, because line 1 can mis-derive in several ways:
      #   (a) BASH_SOURCE[0] empty (heredoc / eval) → CONFIG_PATH=""
      #   (b) BASH_SOURCE[0] without a `/` (file in cwd, "user.env")
      #       → `%/*` returns "user.env" unchanged (not a dir)
      #   (c) BASH_SOURCE[0]="/dev/stdin" (sourced via stdin) → "/dev": a real
      #       directory, non-empty, ≠ BASH_SOURCE[0] — slips past any predicate
      #       that only checks emptiness/self-equality, but /dev has no user.env
      # `[ -f "$CONFIG_PATH/user.env" ]` is true exactly when CONFIG_PATH is
      # the dir we live in, so it catches (a)/(b)/(c) and any future shape while
      # leaving a correctly-derived CONFIG_PATH untouched. `{ ...; }` groups the
      # OR so `&&` binds to the combined condition. MUST stay above the source
      # lines (config.clean preserves insertion order — no `sort -u`).
      echo ': ${CONFIG_PATH:="${BASH_SOURCE[0]%/*}"}'
      echo '{ [ -z "$CONFIG_PATH" ] || [ ! -f "$CONFIG_PATH/user.env" ]; } && CONFIG_PATH="$HOME/config"'
      # Also self-anchor OOSH_DIR here in user.env. user.env is sourced by
      # bashrc BEFORE oosh.env (config.add appends `source $CONFIG_PATH/oosh.env`
      # below). With OOSH_DIR set at this point via the parameter-expansion
      # default, oosh.env's own self-anchor is no longer the only line of
      # defence — and crucially, when `config save oosh OOSH` (called by
      # `oo mode <branch>`) overwrites oosh.env without re-adding its
      # self-anchor, this user.env line still gives fresh shells (sudo su,
      # `bash`, new tmux windows) a working OOSH_DIR. Resolves the $HOME/oosh
      # symlink via cd -P; pwd to prevent self-loops (see comment at the
      # bottom of this function for the oosh.env mirror).
      echo ': ${OOSH_DIR:="$(cd "$HOME/oosh" 2>/dev/null && pwd -P || echo "$HOME/oosh")"}'
    } | cat - $CONFIG > $CONFIG.tmp && mv $CONFIG.tmp $CONFIG
    # PATH is NOT saved — it must be built dynamically at login by
    # bashrcTemplate (this.path.add + OOSH_DIR guard). Saving root's
    # PATH here would overwrite the user's PATH in subprocesses.
    #echo "export CONFIG" >>$CONFIG
    echo "export BASH_FILE=\"$BASH_FILE\"" >>$CONFIG


    ### HACK should be in log.init only. but is needed for ssh remote logging
      log.device $LOG_DEVICE
    ###


    config.save oosh OOSH
    # Self-anchor OOSH_DIR at the top of oosh.env. Same reasoning as the
    # CONFIG_PATH self-anchor in user.env: we no longer persist OOSH_DIR's
    # absolute value (saving user's $HOME/oosh leaks across users when the
    # config is shared), but bashrcTemplate sources oosh.env BEFORE invoking
    # `this`, and line 169's `source "$OOSH_DIR/log"` would expand to
    # `source /log` without OOSH_DIR being set first.
    #
    # IMPORTANT: resolve the symlink ($HOME/oosh → real shared-install path)
    # via `cd -P; pwd`. bashrcTemplate's auto-sync logic at line 177
    # compares ACTUAL_TARGET=readlink -f $HOME/oosh against OOSH_DIR; if
    # they differ AND $HOME/oosh is a symlink, it does `rm -f $HOME/oosh
    # && ln -s "$OOSH_DIR" "$HOME/oosh"`. If we anchored to the literal
    # `$HOME/oosh` (symlink path), that ln -s would create a self-loop —
    # observed in the macOS bash-user docker test session manifesting as
    # `Too many levels of symbolic links`.
    #
    # The :- no-op leaves an already-set OOSH_DIR untouched, so contexts
    # that DID source `this` first (oosh script invocations from PATH) are
    # unaffected.
    {
      echo ': ${OOSH_DIR:="$(cd "$HOME/oosh" 2>/dev/null && pwd -P || echo "$HOME/oosh")"}'
    } | cat - "$CONFIG_PATH/oosh.env" > "$CONFIG_PATH/oosh.env.tmp" && mv "$CONFIG_PATH/oosh.env.tmp" "$CONFIG_PATH/oosh.env"
    # Append PATH builders to oosh.env — ensures OOSH_DIR and brew bash are in PATH
    # for both interactive (bashrc) and non-interactive (CI, subprocesses) shells
    {
      echo '# Ensure OOSH_DIR is in PATH'
      echo 'if [ -n "$OOSH_DIR" ] && [[ ":$PATH:" != *":$OOSH_DIR:"* ]]; then'
      echo '  export PATH="$OOSH_DIR:$OOSH_DIR/ng:$PATH"'
      echo 'fi'
      echo '# Ensure BASH_FILE directory is first in PATH (e.g. brew bash on macOS)'
      echo 'if [ -n "$BASH_FILE" ]; then'
      echo '  _bashDir="$(dirname "$BASH_FILE")"'
      echo '  if [[ "$PATH" != "$_bashDir:"* ]]; then'
      echo '    export PATH="$_bashDir:$PATH"'
      echo '  fi'
      echo 'fi'
      # Default LOG_DEVICE / LOG_LIVE for non-bashrc contexts. log.env
      # no longer persists LOG_DEVICE (it's per-session like LOG_LIVE —
      # bleed-across-users + EACCES same as the LOG_LIVE class), so any
      # consumer of $LOG_DEVICE that didn't go through bashrc (workflow
      # steps, ssh exec, etc.) would otherwise hit `bash: $LOG_DEVICE:
      # ambiguous redirect`. Sourcing the log script here invokes its
      # auto-default block at log:11-19 (tty → /proc/self/fd/1 → /dev/null
      # fallback chain) and the LOG_LIVE re-anchor at log:21-23. Cheap
      # and idempotent — bashrcTemplate:169 sources it again, no-op the
      # second time.
      echo '[ -f "$OOSH_DIR/log" ] && source "$OOSH_DIR/log"'
    } >> "$CONFIG_PATH/oosh.env"
    private.ensure.groupWrite "$CONFIG_PATH/oosh.env"
    config.save log LOG
    config.file user.env
    config.add oosh OOSH
    config.add log LOG
    # if [ -x "$(command -v once)" ]; then
    #   once v
    # fi
  fi
  config.info.log
}

config.bash.minimal.version() { # <bash.minimal.version:5> # sets the BASH_MINIMUM_MAJOR_VERSION
  echo "export BASH_MINIMUM_MAJOR_VERSION=$1" >>$CONFIG
}

config.list() # <name:$CONFIG> # lists the content of the config with <name>
{
  config.file.check $1
  cat $CONFIG
}

config.delete() # <name:$CONFIG> # deletes the config with <name>
{
  config.file.check $1
  rm $CONFIG_PATH/$CONFIG_FILE
}

config.update() { # <?name:$CONFIG> <?ENV_PREFIX> # saves and adds the config 
  config.save $1 $2
  config.add $1 $2
  config.info.log
}

config.info.log() # # logs the config when on LOG_LEVEL info or higher
{
  if [ "$LOG_LEVEL" -gt "3" ]; then
    config.list $1
  fi
}

config.edit() # <name:$CONFIG> # edits the config with <name> in vim
{
  #check.debug.level 6
  oo cmd vim
  if config.file.check $1; then
    vim $CONFIG
  else
    vim $CONFIG
    exit $ERROR_CODE_RECONFIG
  fi
}

config.clean() #  # reinitalises a clean config and removes any custom addons
{
  config.file.check $1
  # Order-preserving de-dup (drop duplicate lines, keep first occurrence).
  # NOT `sort -u`: alphabetical sorting reorders the file and pushes the
  # `{ ... } && CONFIG_PATH="$HOME/config"` fallback (leading `{`, high byte)
  # BELOW the `source $CONFIG_PATH/*.env` lines it must run before — silently
  # defeating it for heredoc/eval/bare-name source contexts (see config.save
  # bootstrap header). Insertion order is the contract here.
  awk '!seen[$0]++' "$CONFIG" >"$CONFIG_PATH/$CONFIG_FILE.clean"
  rm $CONFIG_PATH/$CONFIG_FILE
  mv $CONFIG_PATH/$CONFIG_FILE.clean $CONFIG_PATH/$CONFIG_FILE
  private.ensure.groupWrite "$CONFIG_PATH/$CONFIG_FILE"
  if [ "$LOG_LEVEL" -gt "3" ]; then
    config.list $1
  fi
}

config.add() # <name:$CONFIG> # adds the config <name> into the user.env permanently
{
  local file=$1
  shift
  local name=$1

  if [ -n "$file" ] &&  [ -z "$name" ]; then
    #set -x
    warn.log "BASH 5 support only" # on bash 3 this would help: $(tr '[:lower:]' '[:upper:]' <<< "$file")
    name=${file^^}
  else
    shift
  fi

  if [ -z "$file" ]; then
    name="CONFIG"
  fi
  
  console.log "
  config.add [file:$file] [name:$name] to $CONFIG"
  {
    echo source \$CONFIG_PATH/$file.env
  } >>$CONFIG
  config.clean
}

config.file.check() # <name:$CONFIG> # sets the default config file to <name> if it exists
{
  if [ -n "$1" ]; then
    config.file $1.env
    shift
    RETURN=$1
    RETURN_VALUE=0
  else
    RETURN_VALUE=1
  fi
  info.log "config file: $CONFIG"
  create.result $RETURN_VALUE $CONFIG
  return "$(result)"

}

config.file() # <configfile> # sets the default config file to <configfile> if it exists
# if <?option> is "reset", it sets the $CONFIG_FILE backi to user.env
{
  if [ -z "$1" ]; then
    echo "$CONFIG_FILE"
  else
    case "$1" in
      reset)
        config.init
        echo "CONFIG_PATH=$CONFIG_PATH"
        echo "CONFIG_FILE=$CONFIG_FILE"
        echo
        echo "CONFIG=$CONFIG"
        ;;
      *)
        export FILE=$1
        CONFIG_FILE=$FILE

        export CONFIG=$CONFIG_PATH/$CONFIG_FILE
    esac
  fi
}

config.location() # <configfile> # sets the default config location to <configfile> if it exists
# if <?option> is "reset", it sets the $CONFIG_FILE backi to user.env
{
  if [ -z "$1" ]; then
    echo "$CONFIG_FILE"
  else
    case "$1" in
      reset)
        config.init
        echo "CONFIG_PATH=$CONFIG_PATH"
        echo "CONFIG_FILE=$CONFIG_FILE"
        echo
        echo "CONFIG=$CONFIG"
        ;;
      *)
        local oldConfig="$CONFIG"

        export FILE=$(basename $1)
        CONFIG_PATH=$(dirname $1)
        CONFIG_FILE=$FILE

        info.log "old config     : $oldConfig"
        info.log "new config path: $CONFIG_PATH"
        info.log "new config file: $CONFIG_FILE"

        export CONFIG=$CONFIG_PATH/$CONFIG_FILE
        config.save
        bash
    esac
  fi
}

config.parameter.completion.configfile() {
  echo "reset"
  c2 file.completion "$1"
}

config.parameter.completion.envVariable() {
  cat $CONFIG | line replace "export " | line replace "=.*$" | line filter "source"
}

config.parameter.completion.name() {
  ls "$CONFIG_PATH"/*.env 2>/dev/null | xargs -I{} basename {} .env
}

config.parameter.completion.sshConfigName() {
  source ossh 2>/dev/null
  ossh.parameter.completion.sshConfigHost "$@"
}

function config.path.create() # <pathString> # (internal deprecated) creates respective paths
{
    debug.log " function ${FUNCNAME[0]}($1) $@"

    if [[ "$1" = /* ]]; then
        cd "/"
    fi

    path=""
    info.log "creating path in $(pwd): $1"

    for current in ${1//// }; do

        if [ -z "$path" ]; then
            path=$current
        else
            path=$path/$current
        fi
        debug.log "checking path: $path"

        config.folder.create $path
    done
}

function config.folder.create() # <folderName> # (internal deprecated) creates respective directory requested in parameter 
{

    local current=$1
    if [ ! -d $current ]; then
        debug.log "$current does not exist: creating it..."
        mkdir -p $current
    fi
}

config.ssh.host.set() # <sshConfigName> # configures the PROMPT and the name of this host for ssh config
{ 
  export OOSH_SSH_CONFIG_HOST=$1
  config.save
  echo "To apply changes immediatly type:
  
  exit $ERROR_CODE_RECONFIG"
  exit $ERROR_CODE_RECONFIG
  #reconfigure
}

config.get() # <envVariable>  # gets an environment variable from the user.env
{
  local envVariable="$1"
  if [ -n "$envVariable" ]; then
    shift
  else
    error.log "no envVariable specified"
    return 1
  fi

  # Matches both "export X=..." and the legacy malformed "export declare X=..." form.
  local currentDefinition="$( grep -E "^export( declare)? ${envVariable}=" "$CONFIG" 2>/dev/null )"
  info.log "declaration: $currentDefinition"
  if [ -n "$currentDefinition" ]; then
    local val="${currentDefinition#*=}"
    val="${val#\"}"
    val="${val%\"}"
    echo "$val"
  fi
}

config.set() # <envVariable> <value> # adds or sets an environment variable to the user.env 
{
  local envVariable="$1"
  if [ -n "$envVariable" ]; then
    shift
  else
    error.log "no envVariable specified"
    return 1
  fi

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

  # Matches both "export X=..." and the legacy malformed "export declare X=..." form.
  local currentDefinition="$( grep -E "^export( declare)? ${envVariable}=" "$CONFIG" 2>/dev/null )"
  if [ -n "$currentDefinition" ]; then
    if [ -z "$value" ]; then
      warn.log "set blank value on
      $currentDefinition

      "
    fi
    local tmpFile="${CONFIG}.tmp.$$"
    # Replace the whole line (any form) with the clean "export X=value" form — normalizes on update.
    sed -E "s|^export( declare)? ${envVariable}=.*|export ${envVariable}=\"${value}\"|" "$CONFIG" > "$tmpFile" && mv "$tmpFile" "$CONFIG"
  else
    echo "export $envVariable=\"$value\"" >>$CONFIG
  fi
}


config.unset() # <envVariable> # removes <envVariable> from the current $CONFIG file (idempotent)
{
  local envVariable="$1"
  if [ -n "$envVariable" ]; then
    shift
  else
    error.log "no envVariable specified"
    return 1
  fi

  [ ! -f "$CONFIG" ] && return 0

  if grep -q "^export $envVariable=" "$CONFIG" 2>/dev/null; then
    local tmpFile="${CONFIG}.tmp.$$"
    grep -v "^export $envVariable=" "$CONFIG" > "$tmpFile" && mv "$tmpFile" "$CONFIG"
    debug.log "config.unset: removed $envVariable from $CONFIG"
  fi
}


config.discover() # # lists the existing config files and where they are
{
  echo $CONFIG_PATH
  ls $CONFIG_PATH
}


function config.start() # <method> <parameter> # default start and parameter processing
{
  #echo "sourcing init"
  COMMANDS="$@"
  source this
  #check.debug.level 6

  if [ -z "$CONFIG_PATH" ] || ! [ -f "$CONFIG" ]; then
      config.init
  fi
  if [ "$(type -t this.start)" = "function" ]; then
    this.start "$@"
  else
    echo "config.start: this.start not yet there"
  fi
}

config.start "$@"

