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

# SSH ControlMaster socket path for connection reuse (single password entry).
# Use %C (hash of %l%h%p%r, OpenSSH 6.7+) to keep path short — Unix domain
# sockets have a ~104 char limit. Long hostnames (e.g. macstudio.fritz.box)
# with the old %r@%h:%p template exceeded this on Termux/Android.
: ${OSSH_CONTROL_PATH:="${TMPDIR:-/tmp}/ossh-%C"}

### new.method

# ─────────────────────────────────────────────────────────────────────────────
# CONNECTION MANAGEMENT (SSH ControlMaster)
# ─────────────────────────────────────────────────────────────────────────────

ossh.connection.open() # <host> # open persistent SSH connection (enter password once)
{
  local host="$1"
  [ -z "$host" ] && { error.log "Usage: ossh connection.open <host>"; return 1; }

  # Check if connection already open
  if ssh -O check -o ControlPath="$OSSH_CONTROL_PATH" "$host" 2>/dev/null; then
    info.log "Connection to $host already open"
    return 0
  fi

  # Open master connection in background, persist for 10 min.
  # Try default (IPv4+IPv6) first; if it fails (Broken pipe on IPv6 is
  # common on Termux/Android), retry with -4 to force IPv4.
  if ssh -o ControlMaster=yes \
      -o ControlPath="$OSSH_CONTROL_PATH" \
      -o ControlPersist=600 \
      -o StrictHostKeyChecking=accept-new \
      -N -f "$host" 2>/dev/null; then
    success.log "Connection to $host established (reusable for 10 min)"
    return 0
  fi

  # Retry IPv4-only (avoids Broken pipe on IPv6-first platforms)
  info.log "Retrying $host with IPv4 only (-4)..."
  if ssh -4 -o ControlMaster=yes \
      -o ControlPath="$OSSH_CONTROL_PATH" \
      -o ControlPersist=600 \
      -o StrictHostKeyChecking=accept-new \
      -N -f "$host" 2>/dev/null; then
    success.log "Connection to $host established via IPv4 (reusable for 10 min)"
    return 0
  fi

  error.log "Failed to connect to $host (tried default + IPv4)"
  return 1
}
ossh.connection.open.completion.host() {
  private.ossh.config.complete.hosts "$@"
}

ossh.connection.close() # <host> # close the persistent SSH connection
{
  local host="$1"
  [ -z "$host" ] && { error.log "Usage: ossh connection.close <host>"; return 1; }
  ssh -O exit -o ControlPath="$OSSH_CONTROL_PATH" "$host" 2>/dev/null
  info.log "Connection to $host closed"
}
ossh.connection.close.completion.host() {
  private.ossh.config.complete.hosts "$@"
}

ossh.connection.status() # <host> # check if persistent connection is open
{
  local host="$1"
  [ -z "$host" ] && { error.log "Usage: ossh connection.status <host>"; return 1; }
  if ssh -O check -o ControlPath="$OSSH_CONTROL_PATH" "$host" 2>/dev/null; then
    echo "open"
    return 0
  else
    echo "closed"
    return 1
  fi
}
ossh.connection.status.completion.host() {
  private.ossh.config.complete.hosts "$@"
}

private.ossh.rsync() # <src> <dest> # rsync with ControlMaster and auto-mkdir
{
  local src="$1"
  local dest="$2"
  local ssh_opts="ssh -o ControlPath=$OSSH_CONTROL_PATH -o StrictHostKeyChecking=accept-new"

  # Extract host and remote path for mkdir
  local host="${dest%%:*}"
  local remote_path="${dest#*:}"
  local remote_dir
  remote_dir=$(dirname "$remote_path")

  # Create remote directory first (works with both GNU rsync and openrsync)
  if [ "$remote_dir" != "." ] && [ "$remote_dir" != "~" ]; then
    ssh -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$host" "mkdir -p '$remote_dir'" 2>/dev/null
  fi

  # rsync with scp fallback (Termux/Android may not have rsync)
  if command -v rsync >/dev/null 2>&1; then
    rsync -avz -e "$ssh_opts" "$src" "$dest"
  else
    scp -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$src" "$dest"
  fi
}

private.ossh.rsync.pull() # <src> <dest> # rsync pull with ControlMaster; falls back to scp if rsync unavailable
{
  local src="$1"
  local dest="$2"
  if command -v rsync >/dev/null 2>&1; then
    local ssh_opts="ssh -o ControlPath=$OSSH_CONTROL_PATH -o StrictHostKeyChecking=accept-new"
    rsync -avz -e "$ssh_opts" "$src" "$dest"
  else
    local destDir
    destDir=$(dirname "$dest")
    [ -d "$destDir" ] || mkdir -p "$destDir"
    scp -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$src" "$dest"
  fi
}

private.ossh.ssh() # <host> <command...> # ssh with ControlMaster
{
  local host="$1"
  shift
  ssh -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$host" "$@"
}

ossh.scp() # <src> <dest> # scp using persistent ControlMaster connection
{
  local src="$1" dest="$2"
  [ -z "$src" ] || [ -z "$dest" ] && { error.log "Usage: ossh scp <src> <dest>"; return 1; }
  scp -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$src" "$dest"
}

# ─────────────────────────────────────────────────────────────────────────────

ossh.list() { # # lists all ossh methods
  private.ossh.config.complete.hosts "$@" | line find "$1" | line replace "$1" "${CYAN}$1${NORMAL}"
}

ossh.list.ids() # <?id> <?sshDir:~/.ssh> # list available ids
{
  local id="$1"
  shift 2>/dev/null
  private.get.sshDir "$1"
  local sshDir="$RESULT"
  if [ -n "$id" ]; then
    tree -L 1 "$sshDir/ids" | line find "$id"
  else
    tree -L 1 "$sshDir/ids"
  fi
}

ossh.id.public.get() # <?id> <?sshDir:~/.ssh> # prints the public key of <id> to the console
{
  local idName="$1"
  [ -n "$1" ] && shift
  private.get.sshDir "$1"
  local sshDir="$RESULT"
  local idFile
  if [ -z "$idName" ]; then
    private.detect.ssh.key.type "$sshDir"
    local keyType="${RESULT:-id_rsa}"
    idFile="$sshDir/${keyType}.pub"
    idName=user
  else
    private.detect.ssh.key.type "$sshDir/ids/$idName"
    local keyType="${RESULT:-id_rsa}"
    idFile="$sshDir/ids/$idName/${keyType}.pub"
  fi
  echo $idFile

  cat "$idFile"
}

ossh.id.create.fromKey() # <idName> <sourceSshDir> <?sshDir:~/.ssh> # creates a new key pair in <sshDir>/ids/<idName> from a given .ssh-dir with auto-detected key
{
  if [ -z "$1" ]; then
    error.log "no idName"
    return 1
  else
    idName="$1"
    shift
  fi

  if [ -z "$1" ]; then
    error.log "no sshdir"
    return 1
  else
    sshdir="$1"
    shift
  fi

  if ! private.detect.ssh.key "$sshdir"; then
    error.log "no ssh key found in $sshdir"
    return 1
  fi
  local sourceKey="$RESULT"
  local keyType="${sourceKey##*/}"

  if [ ! -f "${sourceKey}.pub" ]; then
    error.log "missing public key ${sourceKey}.pub"
    return 1
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshBase="$RESULT"

  echo "create $sshBase/ids/$idName/$keyType"
  mkdir -p $sshBase/ids/$idName
  cp "$sourceKey" "$sshBase/ids/$idName/"
  cp "${sourceKey}.pub" "$sshBase/ids/$idName/"

  mkdir -p $sshBase/ids/$idName/public_keys
  mkdir -p $sshBase/ids/$idName/private_key

  cp "$sshBase/ids/$idName/$keyType" "$sshBase/ids/$idName/private_key/$idName.private_key"
  cp "$sshBase/ids/$idName/${keyType}.pub" "$sshBase/ids/$idName/public_keys/$idName.public_key"
}

ossh.id.create() # <idName> <?sshDir:~/.ssh> # creates a new ed25519 ssh key pair in <sshDir>/ids/<idName>
{
  if [ -z "$1" ]; then
    error.log "no idName"
    return 1
  else
    idName="$1"
    shift
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshBase="$RESULT"
  local keyType="id_ed25519"

  echo "create $sshBase/ids/$idName/$keyType"
  mkdir -p $sshBase/ids/$idName
  ssh-keygen -t ed25519 -N "" -f "$sshBase/ids/$idName/$keyType"

  mkdir -p $sshBase/ids/$idName/public_keys
  mkdir -p $sshBase/ids/$idName/private_key

  cp "$sshBase/ids/$idName/$keyType" "$sshBase/ids/$idName/private_key/$idName.private_key"
  cp "$sshBase/ids/$idName/${keyType}.pub" "$sshBase/ids/$idName/public_keys/$idName.public_key"
}

ossh.config.create() { # <?sshConfigName|user@host> <?url> <?id> # creates a ssh config. Single user@host arg auto-parses. Two args: <alias> <user@host>
  #set -x
  local sshConfigHost=""
  local url=""
  local id=""

  # Smart detection: if $1 contains @ and $2 is empty, treat $1 as URL and derive alias
  if [[ "$1" == *@* ]] && [ -z "$2" ]; then
    url="$1"
    sshConfigHost="${1#*@}"         # strip user@ prefix → "host" or "host:port"
    sshConfigHost="${sshConfigHost%%:*}"   # strip :port if present
    shift
  else
    # Two-arg form: <alias> <url>, or no args for system defaults
    if [ -n "$1" ]; then
      sshConfigHost="$1"
      shift
    else
      sshConfigHost="$OOSH_SSH_CONFIG_HOST"
      if [ -z "$sshConfigHost" ]; then
        sshConfigHost="$HOSTNAME"
      fi
    fi

    if [ -n "$1" ]; then
      url="$1"
      shift
    else
      url="$USER@$(hostname):$( ossh.server.port.get )"
    fi
  fi

  if [ -n "$1" ]; then
    id="$1"
    shift
  else
    private.get.sshDir
    if private.detect.ssh.key "$RESULT"; then
      id="$RESULT"
    else
      id="$RESULT/id_ed25519"
    fi
  fi

  if [ -d "$id" ]; then
    # id_rsa etc. can be a DIRECTORY on some platforms (Termux) — detect key inside it
    if private.detect.ssh.key "$id"; then
      id="$RESULT"
    else
      warn.log "ossh: '$id' is a directory but contains no SSH key"
    fi
  elif [ ! -f "$id" ]; then
    private.get.sshDir
    local idName="${id##*/}"
    if private.detect.ssh.key "$RESULT/ids/$idName"; then
      id="$RESULT"
    else
      id="$RESULT/ids/$idName/id_ed25519"
    fi
  fi

  ossh.config.parse.url "$url"

}

ossh.key.folder.create() { # <?sshDir:~/.ssh> # if the .ssh folder was not created with ossh it fixes its structure
  private.get.sshDir "$1"
  if [ -n "$1" ]; then
    shift
  fi
  local sshDir="$RESULT"
  
  mkdir -p $sshDir/public_keys                                      
  mkdir -p $sshDir/private_key
  if ! private.detect.ssh.key "$sshDir"; then
    error.log "no ssh key found in $sshDir"
    return 1
  fi
  local keyFile="$RESULT"
  local keyType="${keyFile##*/}"
  ossh.key.name.get                                   
  local sshKeyName="$RESULT"
  cp "$keyFile" "$sshDir/private_key/$sshKeyName.private_key"
  cp "${keyFile}.pub" "$sshDir/public_keys/$sshKeyName.public_key"
  ossh.status
}

ossh.key.folder.delete() { # <?sshDir:~/.ssh> # cleans the .ssh folder to a standard layout....may cause data loss!
  private.get.sshDir "$1"
  if [ -n "$1" ]; then
    shift
  fi
  local sshDir="$RESULT"
  problem.log "are you shure to delete the key folders in: $sshDir"
  rm -rf  $sshDir/public_keys                                      
  rm -rf $sshDir/private_key
  ossh.status
}

ossh.install.create() { #  <sshConfigHost> <url> # creates a config <sshConfigName> for <url> and pushes the oosh there 

  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no sshConfigHost was specified"
    return 1
  fi

  local url="$1"
  if [ -n "$1" ]; then
    url="$1"
    shift
  else
    error.log "no url was specified"
    return 1
  fi

  ossh.config.create $sshConfigHost
  ossh.config.save.last
  ossh.config.parse.url $url ossh.install
  
}

ossh.install() { # <sshConfigHost> # pushes the oosh installer to the <sshConfigHost>
  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
    ossh.config.parse $sshConfigHost private.push.init.oosh
    return $?
  else
    error.log "no sshConfigHost was specified"
    return 1
  fi
  
  if [ -z "$url" ]; then
    error.log "no url"
    return 1
  fi

  if [ -z "$user" ]; then
    ossh.config.parse.url $url private.push.init.oosh
  else
    private.push.init.oosh
  fi

}

private.push.init.oosh() {

  private.get.sshDir
  local file="$RESULT/public_keys/$( ossh.id.file.get ).public_key"
  info.log "file=$file"

  # Open persistent connection (password entered once)
  ossh.connection.open "$sshConfigHost"

  if private.ossh.rsync "$OOSH_DIR/init/oosh" "$sshConfigHost:." && \
     private.ossh.rsync "$file" "$sshConfigHost:."; then
    success.log "successfully copied oosh"
    private.ossh.ssh "$sshConfigHost" "./oosh mode ssh $sshConfigHost $OOSH_SSH_CONFIG_HOST $OOSH_MODE"

    important.log "remote installation done: $?"
  else
    problem.log "maybe already installed?"

    private.ossh.ssh "$sshConfigHost" "bash /home/$user/oosh/ossh continue.local.install from $(ossh.url.get)"
  fi

  source $CONFIG
  config list
  
  # problem.log "ossh 
  # in private.push.init.oosh
  # before ossh.install.finish.local $sshConfigHost"

  ossh.install.finish.local $sshConfigHost

}

ossh.install.continue.local() { # <remoteSshConfigName> <sshConfigNameUsedForLocal> #
  success.log "remote inititialized over ssh....
  
  here we go: 
  $CONFIG
  $*"
  #set -x 
  source $CONFIG

  local remoteSshConfigName="$1"
  if [ -n "$1" ]; then
    shift
  else
    remoteSshConfigName="unknownRemote"
  fi

  local sshConfigNameUsedForLocal="$1"
  if [ -n "$1" ]; then
    shift
  else
    if [ -n "$OOSH_SSH_CONFIG_HOST" ]; then
      sshConfigNameUsedForLocal="$OOSH_SSH_CONFIG_HOST"
    else
      sshConfigNameUsedForLocal="$HOSTNAME"
    fi
  fi

  oo update
  #source user
  oo state
  state next
  #state next

  source $CONFIG
  config list
  #problem.log "returned from state transition: $?   -$sshConfigNameUsedForLocal"

  config ssh.host.set "$sshConfigNameUsedForLocal"

  check dir $HOME/ssh.original not exists \
    call user ssh.backup original
  
  user init

  check dir $HOME/ssh.$USER.$sshConfigNameUsedForLocal.for.$remoteSshConfigName not exists \
    call user ssh.backup $USER.$sshConfigNameUsedForLocal.for.$remoteSshConfigName
  
  mv $HOME/*.public_key $HOME/.ssh/public_keys

  user update.authorized_keys
  #state set 12
  #scp -r $HOME/ssh.original 
  create.result 0 "ossh.install.continue.local done successful" "$1" 
  return $(result)

}

ossh.install.finish.local() { # # finishes local oosh installation
  important.log "entering ossh.install.finish.local"

  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no sshConfigHost was specified"
    return 1
  fi

  ossh.config.push $sshConfigHost
  remoteKeyName=$( ossh.exec $sshConfigHost "ossh get.key.name" )
  ossh.key.pull $sshConfigHost $remoteKeyName
  ossh.authorized_keys.update
}

ossh.authorized_keys.update() # <?sshDir:~/.ssh> # updates authorized_keys from public_keys dir
{

  private.get.sshDir "$1"
  if [ -n "$1" ]; then
    shift
  fi
  local sshDir="$RESULT"

  if ossh.isInstalled "" "$sshDir"; then
    info.log "updating $sshDir/authorized_keys"
    rm $sshDir/authorized_keys
    cat $sshDir/public_keys/* >>$sshDir/authorized_keys

    chmod 700 $sshDir
    chmod 600 $sshDir/authorized_keys
    if private.detect.ssh.key "$sshDir"; then
      chmod 600 "$RESULT"
    fi
  fi
}





ossh.permission.fix() { # <?sshDir:~/.ssh> # fixes file permissions in sshDir
  private.get.sshDir "$1"
  if [ -n "$1" ]; then
    shift
  fi
  local sshDir="$RESULT"

  if ossh.isInstalled "" "$sshDir"; then
    chmod 700 $sshDir
    [ -f $sshDir/authorized_keys ] && chmod 600 $sshDir/authorized_keys
    chmod +X,go+r $sshDir/*
    if private.detect.ssh.key "$sshDir"; then
      chmod 600 "$RESULT"
    fi
    tree -p $sshDir
  fi
}

ossh.id.file.get() { # # gets the actual file name (instead of the best key name from get.key.name )
  local sshDir="$1"
  if [ -z "$1" ]; then
    sshDir="$CURRENT_SSH_DIR"
  fi
  if [ -z "$sshDir" ]; then
    sshDir="$HOME/.ssh"
  fi
  shift


  local sshKeyName="$(ls $sshDir/private_key/ | sed 's/\.private_key//')"
  if [ -z "$sshKeyName" ]; then
    error.log "get.file.name: file not found..."
    important.log "recovering by generating key.name for file"
    sshKeyName="$( ossh.key.name.get )"
  fi

  create.result 0 "$sshKeyName" "$1"
  echo "$RESULT"
  return $(result)
}

ossh.show() { # # shows ossh status and available configs
  #sed -n -e '/Host PI/,/Host / p' .ssh/config 
  awk "/Host $1$/,/^$/" .ssh/config 
}

ossh.install.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.parameter.completion.sshConfigHost() {
    private.get.sshDir
    local sshDir="$RESULT"
    grep '^Host ' $sshDir/config $sshDir/config.d/* 2>/dev/null | cut -d ' ' -f 2- | grep -v '^\*'
}

ossh.parameter.completion.keyName() {
  private.get.sshDir
  ls $RESULT/public_keys/
}

ossh.parameter.completion.toHost() {
    ossh.parameter.completion.sshConfigHost "$@"
}

ossh.parameter.completion.formHost() {
    ossh.parameter.completion.sshConfigHost "$@"
}

ossh.parameter.completion.sshConfigName() {
    ossh.parameter.completion.sshConfigHost "$@"
}

ossh.parameter.completion.sshDir() {
  echo "$HOME/.ssh"
  ls -d $HOME/.ssh/ids/*/ 2>/dev/null
}

ossh.parameter.completion.id() {
  private.get.sshDir
  ls $RESULT/ids
}

ossh.parameter.completion.file() {
  important.log "ossh.parameter.completion.file $*"
  c2 files.completion "$1"
  private.get.sshDir
  echo $RESULT/config
}

ossh.parameter.completion.dir() {
  important.log "ossh.parameter.completion.file $*"
  c2 folders.completion "$1"
  private.get.sshDir
  echo $RESULT/
}

# ─────────────────────────────────────────────────────────────────────────────
# PER-FIELD SSH CONFIG ACCESS — config.<field>.get / config.<field>.set
# Each field is its own method for type-specific tab completion
# ─────────────────────────────────────────────────────────────────────────────

private.ossh.config.complete.hosts() {
  private.get.sshDir
  grep '^Host ' "$RESULT/config" 2>/dev/null | sed 's/^Host //' | grep -v '^\*'
}

private.ossh.config.field.get() # <configItem> <field> # get a single field from Host block
{
  local host="$1" field="$2"
  [ -z "$host" ] || [ -z "$field" ] && return 1
  private.get.sshDir
  local file="$RESULT/config"
  local value
  value=$(awk "/^Host ${host}\$/,/^\$/" "$file" | grep -i "^ *${field} " | sed 's/^ *//' | cut -d' ' -f2-)
  if [ -n "$value" ]; then
    echo "$value"
  else
    error.log "Field '$field' not found in Host $host"
    return 1
  fi
}

private.ossh.config.field.set() # <configItem> <field> <value> # set a single field in Host block
{
  local host="$1" field="$2" value="$3"
  [ -z "$host" ] || [ -z "$field" ] || [ -z "$value" ] && return 1
  private.get.sshDir
  local file="$RESULT/config"
  if ! grep -q "^Host ${host}$" "$file" 2>/dev/null; then
    error.log "Host '$host' not found in $file"
    return 1
  fi
  local field_exists
  field_exists=$(awk "/^Host ${host}\$/,/^\$/" "$file" | grep -ci "^ *${field} ")
  if [ "$field_exists" -gt 0 ]; then
    awk -v host="$host" -v field="$field" -v value="$value" '
      /^Host / { in_block = ($2 == host) }
      in_block && tolower($1) == tolower(field) { $0 = " " field " " value }
      /^$/ && in_block { in_block = 0 }
      { print }
    ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
    success.log "Updated $field in Host $host"
  else
    awk -v host="$host" -v field="$field" -v value="$value" '
      { print }
      /^Host / && $2 == host { print " " field " " value }
    ' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
    success.log "Added $field to Host $host"
  fi
}

# --- IdentityFile ---
private.ossh.config.identityFile.get() { # <configItem> # get IdentityFile for Host
  private.ossh.config.field.get "$1" "IdentityFile"; }
private.ossh.config.identityFile.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.identityFile.set() { # <configItem> <value> # set IdentityFile for Host
  private.ossh.config.field.set "$1" "IdentityFile" "$2"; }
private.ossh.config.identityFile.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.identityFile.set.completion.value() {
  private.get.sshDir; local d="$RESULT"
  for k in "$d"/ids/*/id_*; do [ -f "$k" ] && ! [[ "$k" == *.pub ]] && echo "$k"; done
}

# --- User ---
private.ossh.config.user.get() { # <configItem> # get User for Host
  private.ossh.config.field.get "$1" "User"; }
private.ossh.config.user.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.user.set() { # <configItem> <value> # set User for Host
  private.ossh.config.field.set "$1" "User" "$2"; }
private.ossh.config.user.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.user.set.completion.value() { echo "git"; echo "root"; echo "deploy"; echo "ubuntu"; }

# --- Port ---
private.ossh.config.port.get() { # <configItem> # get Port for Host
  private.ossh.config.field.get "$1" "Port"; }
private.ossh.config.port.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.port.set() { # <configItem> <value> # set Port for Host
  private.ossh.config.field.set "$1" "Port" "$2"; }
private.ossh.config.port.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.port.set.completion.value() { echo "22"; echo "443"; echo "2222"; echo "8022"; }

# --- HostName ---
private.ossh.config.hostName.get() { # <configItem> # get HostName for Host
  private.ossh.config.field.get "$1" "HostName"; }
private.ossh.config.hostName.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.hostName.set() { # <configItem> <value> # set HostName for Host
  private.ossh.config.field.set "$1" "HostName" "$2"; }
private.ossh.config.hostName.set.completion.configItem() { private.ossh.config.complete.hosts; }

# --- ProxyJump ---
private.ossh.config.proxyJump.get() { # <configItem> # get ProxyJump for Host
  private.ossh.config.field.get "$1" "ProxyJump"; }
private.ossh.config.proxyJump.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.proxyJump.set() { # <configItem> <value> # set ProxyJump for Host
  private.ossh.config.field.set "$1" "ProxyJump" "$2"; }
private.ossh.config.proxyJump.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.proxyJump.set.completion.value() { private.ossh.config.complete.hosts; }

# --- ForwardAgent ---
private.ossh.config.forwardAgent.get() { # <configItem> # get ForwardAgent for Host
  private.ossh.config.field.get "$1" "ForwardAgent"; }
private.ossh.config.forwardAgent.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.forwardAgent.set() { # <configItem> <value> # set ForwardAgent for Host
  private.ossh.config.field.set "$1" "ForwardAgent" "$2"; }
private.ossh.config.forwardAgent.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.forwardAgent.set.completion.value() { echo "yes"; echo "no"; }

# --- IdentitiesOnly ---
private.ossh.config.identitiesOnly.get() { # <configItem> # get IdentitiesOnly for Host
  private.ossh.config.field.get "$1" "IdentitiesOnly"; }
private.ossh.config.identitiesOnly.get.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.identitiesOnly.set() { # <configItem> <value> # set IdentitiesOnly for Host
  private.ossh.config.field.set "$1" "IdentitiesOnly" "$2"; }
private.ossh.config.identitiesOnly.set.completion.configItem() { private.ossh.config.complete.hosts; }
private.ossh.config.identitiesOnly.set.completion.value() { echo "yes"; echo "no"; }

# ── PUBLIC DISPATCHERS — one config.set and config.field.get for all 7 fields ──

ossh.config.set() { # <configItem> <field> <value> # set a field in SSH config Host block
  local configItem="$1" field="$2" value="$3"
  [ -z "$configItem" ] || [ -z "$field" ] || [ -z "$value" ] && { error.log "Usage: ossh config.set <configItem> <field> <value>"; return 1; }
  # Field names are camelCase in helpers (e.g. identityFile, hostName)
  local setter="private.ossh.config.${field}.set"
  if type -t "$setter" &>/dev/null; then
    "$setter" "$configItem" "$value"
  else
    error.log "Unknown field: $field"
    return 1
  fi
}
ossh.config.set.completion.configItem() { private.ossh.config.complete.hosts; }
ossh.config.set.completion.field() {
  echo "identityFile"; echo "user"; echo "port"; echo "hostName"
  echo "proxyJump"; echo "forwardAgent"; echo "identitiesOnly"
}
ossh.config.set.completion.value() {
  # Dispatch to field-specific value completion
  local field="$2"
  local valComp="private.ossh.config.${field}.set.completion.value"
  type -t "$valComp" &>/dev/null && "$valComp"
}

ossh.config.field.get() { # <configItem> <field> # get a single field from SSH config Host block
  local configItem="$1" field="$2"
  [ -z "$configItem" ] || [ -z "$field" ] && { error.log "Usage: ossh config.field.get <configItem> <field>"; return 1; }
  local getter="private.ossh.config.${field}.get"
  if type -t "$getter" &>/dev/null; then
    "$getter" "$configItem"
  else
    error.log "Unknown field: $field"
    return 1
  fi
}
ossh.config.field.get.completion.configItem() { private.ossh.config.complete.hosts; }
ossh.config.field.get.completion.field() {
  echo "identityFile"; echo "user"; echo "port"; echo "hostName"
  echo "proxyJump"; echo "forwardAgent"; echo "identitiesOnly"
}

ossh.config.get.completion() {
  ossh.parameter.completion.sshConfigHost "$@"
}

ossh.config.get() { # <sshConfigHost> <?file:~/.ssh/config> <?sshDir:~/.ssh>  # outputs the ssh config
  local host="$1"
  if [ -n "$1" ]; then
    shift
  else
    ossh.config.create
    return $?
  fi

  local file="$1"
  if [ -n "$1" ]; then
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi
  cat $file | line.find "Host $host$" "^$" | tee $CONFIG_PATH/result.txt

  if [ -s $CONFIG_PATH/result.txt ]; then
    create.result 0 "config $host found"
  else
    create.result 2 "config $host not found"
    warn.log "$RESULT"
  fi

  return $(result)
}

ossh.config.parse.url() {  # <url>  <?method> # parses the url and shows config values. Then calls optional method that has acces to the paresed values
  local url="$1"
  if [ -n "$1" ]; then
    url="$1"
    shift
  else
    url="$USER@$(hostname):22"
  fi

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


  local user="$( echo $url | cut -d"@" -f1  )"
  local hostAndPort="$( echo $url | cut -d"@" -f2  )"

  local hostname="$( echo $hostAndPort | cut -d":" -f1  )"
  local port="$( echo $hostAndPort | cut -d":" -f2  )"
  if ! this.isNumber $port; then
    path=$port
    SSH_CONFIG_Port=22
    port="$SSH_CONFIG_Port"
  fi
  
  if [ -z "$id" ]; then
    private.get.sshDir
    if private.detect.ssh.key "$RESULT"; then
      id="$RESULT"
    else
      id="$RESULT/id_ed25519"
    fi
  fi

  private.config.create
  $call
}

ossh.config.parse() { # <sshConfigHost>  <?method:private.config.create> # parses the config and shows config values. Then calls optional <method> that has acces to the paresed values

  #set -x
  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no sshConfigHost was specified"
    return 1
  fi


  local call="$1"
  if [ -n "$1" ]; then
    call="$1"
    shift
  else
    call="private.config.create"
  fi

  ossh.config.get $sshConfigHost | line.trim | line.split | line.unquote | line.format "declare -- SSH_CONFIG_%s=%s\n" >$CONFIG_PATH/result.env
  source $CONFIG_PATH/result.env

  local sshConfigHost="$SSH_CONFIG_Host"
  local user="$SSH_CONFIG_User"
  local hostname="$SSH_CONFIG_HostName"
  
  local path=""
  local port="$SSH_CONFIG_Port"
  if ! this.isNumber port; then
    path=$port
    SSH_CONFIG_Port=22
    port="$SSH_CONFIG_Port"
  fi
  local id="$SSH_CONFIG_IdentityFile"

  $call
}

private.config.create() {
  {
    echo ""
    echo "Host $sshConfigHost"
    echo " User $user"
    echo " Port $port"
    echo " HostName $hostname"
    echo " IdentityFile $id"
    echo ""
  } >$CONFIG_PATH/result.txt
  ossh.config.show.last
  create.result 0 "$sshConfigHost"
}

ossh.url.get() { # <sshConfigHost> # gets the URL for the given <sshConfigHost>
  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    echo "$USER@$HOSTNAME"
    #error.log "no sshConfigHost was specified"
    #return 0
  fi
  ossh.config.parse $sshConfigHost private.get.url
}

ossh.ssh.parameter.get() { # <sshConfigHost> # gets the ssh paramerters for the given <sshConfigHost>
  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    echo "$USER@$HOSTNAME -p=22"
    return 0
  fi
  ossh.config.parse $sshConfigHost private.get.ssh.parameter
  
}

private.get.url() {
  echo "$SSH_CONFIG_User@$SSH_CONFIG_HostName:$SSH_CONFIG_Port"
}

private.get.ssh.parameter() {
  echo "$SSH_CONFIG_User@$SSH_CONFIG_HostName -p $SSH_CONFIG_Port"
}

private.get.sshDir() {
  local sshDir="$1"
  if [ -z "$1" ]; then
    sshDir="$CURRENT_SSH_DIR"
  fi
  if [ -z "$sshDir" ]; then
    sshDir="$HOME/.ssh"
  fi
  shift

  create.result 0 "$sshDir" "$1"
  info.log "$RESULT"
  return $(result)
}

ossh.url.get.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.ssh.parameter.get.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.config.parse.completion() {
  private.ossh.config.complete.hosts "$@"
}


ossh.login() { # <sshConfigHost> # like normal ssh connect
  local sshConfigHost="$1"
  if [ -n "$1" ]; then
    sshConfigHost="$1"
    shift
  else
    error.log "no sshConfigHost was specified"
    return 1
  fi

  private.get.sshDir
  local sshConfigHostIP=$(grep -A3 "$sshConfigHost" "$RESULT/config" | grep -A0 HostName | awk '{print $2}')
  ssh -o ControlPath="$OSSH_CONTROL_PATH" -o StrictHostKeyChecking=accept-new "$sshConfigHost"
  # if ssh-keygen -F "$sshConfigHostIP" > /dev/null; then
  #   ssh "$sshConfigHost"
  # else
  #   local loop=true
  #   while [[ $loop == true ]]
  #   do
  #     local answer
  #     # echo "Are you sure you want to continue connecting and add remote fingerprint to known_hosts (Y[es]/N[o])?"
  #     # read -e -i "yes" answer
  #     info.log "Choice to add fingerprint to known_hosts answer: $answer"
  #     answer=$(echo $answer | awk '{print tolower($0)}')

  #     if [ $answer = "yes" -o $answer = "y" ]; then
  #       loop=false
  #       ssh -o StrictHostKeyChecking=accept-new "$sshConfigHost"
  #     elif [ $answer = "no" -o $answer = "n" ]; then
  #       echo "Do you want to use a fingerprint without adding it to known_hosts (Y[es] or abort with N[o])?"
  #       read -e -i "no" answer
  #       info.log "Using fingerpint without adding to known_hosts answer: $answer"
  #       answer=$(echo $answer | awk '{print tolower($0)}')

  #       if [ $answer = "yes" -o $answer = "y" ]; then
          
  #         echo "Give fingerprint"
  #         read -e answer
  #         info.log "Fingerprint answer: $answer"

  #         if [[ "$(ssh-keyscan "$sshConfigHostIP")" =~ "$answer" ]]; then
  #           loop=false
  #           echo "Given fingerprint maches"
  #           ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q "$sshConfigHost"
  #           info.log "Given fingerprint matches"
  #         else
  #           echo "Given fingerprint doesn't match. Starting from the beginning again"
  #           info.log "Given fingerprint doesn't match. Starting from the beginning again"
  #         fi

  #       elif [ $answer = "no" -o $answer = "n" ]; then
  #         loop=false
  #         echo "Aborting ssh login"
  #         info.log "Aborted ssh login"
  #       else
  #         echo "Unkown response, please try again"
  #         info.log "Restarted ssh login"
  #       fi
  #     else
  #       echo "Unkown response, please try again"
  #       info.log "Restarted ssh login"
  #     fi
  #   done

  # fi
  RETURN=$1
}

ossh.login.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.config.show.last() {
  cat $CONFIG_PATH/result.txt
}

ossh.config.save.last() {  # <?file> # appends the config to file
  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi

  # Check for duplicate Host entry before appending
  local hostEntry=$(grep "^Host " "$CONFIG_PATH/result.txt" 2>/dev/null | head -1 | sed 's/^Host //')
  if [ -n "$hostEntry" ] && grep -q "^Host ${hostEntry}$" "$file" 2>/dev/null; then
    warn.log "Host '$hostEntry' already exists in $file — not appending duplicate"
    return 1
  fi

  cat $CONFIG_PATH/result.txt >>$file
  important.log "appended last config to $file"
}


ossh.config.edit() { # <?file> # opens the config file in an editor
  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi
  oo cmd vim
  vim $file
}

ossh.config.list() { # <?file> # prints the config file
  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi

  cat $file
}

ossh.status() { # # checks if the users .ssh is configures and if the ssh service is up
  source os
  source oo
  oo.cmd tree
  oo.cmd sshd openssh-server
  private.get.sshDir
  local sshDir="$RESULT"
  if user ssh.status log "$sshDir"; then
    tree -p "$sshDir"
  fi

  if os.check ossh.service.status; then
    $RESULT "$@"
  else
    source $CONFIG_PATH/result.env
    important.log "$RESULT is not supported"
  fi  

}

# ─────────────────────────────────────────────────────────────────────────────
# KEY TYPE AUTO-DETECTION
# ─────────────────────────────────────────────────────────────────────────────

private.detect.ssh.key() # <sshDir> # auto-detects the private key in sshDir. Sets RESULT to full path (e.g. /path/id_ed25519). Returns 1 if none found.
{
  local sshDir="$1"
  local keyType
  for keyType in id_ed25519 id_ecdsa id_rsa id_dsa; do
    if [ -f "$sshDir/$keyType" ]; then
      RESULT="$sshDir/$keyType"
      return 0
    fi
  done
  RESULT=""
  return 1
}

private.detect.ssh.key.type() # <sshDir> # like private.detect.ssh.key but RESULT is just the key type name (e.g. id_ed25519)
{
  private.detect.ssh.key "$1"
  local rc=$?
  if [ $rc -eq 0 ]; then
    RESULT="${RESULT##*/}"
  fi
  return $rc
}

ossh.isInstalled() # <?log> <?sshDir:~/.ssh> # returns 1 if there is no ssh init. use log as argument if you want to see the output
{
  local logArg=""
  if [ "$1" == "log" ]; then
    logArg="log"
    shift
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshDir="$RESULT"

  if private.detect.ssh.key "$sshDir"; then
    create.result 0 "ssh is initialized for $USER in $sshDir ($(basename $RESULT))" "$1"
    if ! [ -d $sshDir/public_keys ]; then
      user ssh.create.folders "$sshDir"
    fi
  else
    create.result 1 "ssh does not exist for $USER in $sshDir" "$1"
  fi

  if [ "$logArg" == "log" ]; then
    echo "$RESULT"
    RETURN=$1
  else
    info.log "$RESULT"
  fi
  return "$(result)"
}

ossh.service.status.unknown() {
  warn.log "could not determine OS....assuming linux"
  ossh.service.status.linux
}

ossh.service.status.linux() {
  if  [ -x "$(command -v service)" ]; then
    if ! service ssh status; then
      service ssh restart
    fi
    return $?
  fi
  if  [ -x "$(command -v systemctl)" ]; then
    if ! systemctl status sshd; then
      systemctl restart sshd
    fi
    return $?
  fi
  sshd &
}

ossh.service.status.darwin() {
  if ! $SUDO systemsetup -getremotelogin; then
    $SUDO launchctl stop com.openssh.sshd
    $SUDO launchctl start com.openssh.sshd
    #sudo systemsetup -setremotelogin on
  fi
}

ossh.config.push() { # <toHost> <sshConfigName> # adds the config <configName> to the configs on host <toHost>


  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

  local configName="$1"
  if [ -n "$1" ]; then
    shift
      ossh.config.get $configName >$CONFIG_PATH/tmp.ssh.config.$configName
  else
    ossh.config.create >$CONFIG_PATH/tmp.ssh.config.$configName
  fi


  ossh.connection.open "$toHost"
  private.ossh.rsync "$CONFIG_PATH/tmp.ssh.config.$configName" "$toHost:config/tmp.ssh.config.$configName"
  private.ossh.ssh "$toHost" "cat config/tmp.ssh.config.$configName >>.ssh/config; rm ~/config/tmp.ssh.config.$configName"
  rm ~/config/tmp.ssh.config.$configName
}

ossh.config.push.completion() {
  private.ossh.config.complete.hosts "$@"
}


ossh.config.push.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.key.push() { #  <toHost> <?keyName> <?sshDir:~/.ssh> # adds your key to host <toHost> optional you can specify another <?keyName> from <sshDir>/public_keys/
  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

  local keyName="$1"
  if [ -n "$1" ]; then
    shift
  else
    keyName="$( ossh.id.file.get ).public_key"
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshDir="$RESULT"

  echo $sshDir/public_keys/$keyName $toHost:.ssh/public_keys/$keyName
  ossh.connection.open "$toHost"
  private.ossh.rsync "$sshDir/public_keys/$keyName" "$toHost:.ssh/public_keys/$keyName"
  ossh.exec $toHost "user update.authorized_keys"
}

ossh.key.name.get() { # # creates a good key name fot this user and host
  local sshKeyName="$SSH_KEY_NAME"

  # if [ -z "$SSH_KEY_NAME" ]; then
  #   source config
  #   config.load ssh.info
  # fi

  if [ -z "$sshKeyName" ]; then
    if [ -n "$OOSH_SSH_CONFIG_HOST" ]; then
      ossh.key.name.set "$USER.$OOSH_SSH_CONFIG_HOST"
    else
      ossh.key.name.set "$USER.$(hostname -f)"
    fi
  fi

  create.result 0 "$RESULT" "$1"
  echo "$RESULT"
  return $(result)
}


ossh.key.name.set() {  # <?sshKeyName> # sets the key name to sshKeyName and saves it in $CONFIG_PATH/ssh.info.env
  local sshKeyName="$1"
  if [ -z "$sshKeyName" ]; then
    export SSH_KEY_NAME="$USER.$(hostname -f)"
  fi
  config save "ssh.info" "SSH_" >/dev/null
  stop.log "$SSH_KEY_NAME - $sshKeyName"
  create.result 0 "$sshKeyName" "$1"
  return $(result)
}

# ossh.id.file.get() { # # gets the actual key file name (instead of the best key name from get.key.name )
#   local sshDir="$1"
#   if [ -z "$1" ]; then
#     sshDir="$CURRENT_SSH_DIR"
#   fi
#   if [ -z "$sshDir" ]; then
#     sshDir="$HOME/.ssh"
#   fi
#   shift


#   local sshKeyName="$(ls $sshDir/private_key/ | sed 's/\.private_key//')"

#   create.result 0 "$sshKeyName" "$1"
#   echo "$RESULT"
#   return $(result)
# }


ossh.dir.push() { # <toHost> <dir> # adds the config <configName> to the configs on host <toHost>

  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

  local dir="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no dir was specified"
    return 1
  fi

  ossh.connection.open "$toHost"
  private.ossh.rsync "$dir" "$toHost:."
}


ossh.dir.push.completion() {
  private.ossh.config.complete.hosts "$@"
}


ossh.key.push.completion() {
  #ls $HOME/.ssh/public_keys/$1*
    private.ossh.config.complete.hosts "$@"
}

ossh.dir.pull() { # <fromHost> <dir> # gets te directory <dir> <fromHost> into the CURRENT LOCAL dirrectory

  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

  local dir="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no dir was specified"
    return 1
  fi

  important.log "copying from $toHost:$dir"
  ossh.connection.open "$toHost"
  private.ossh.rsync.pull "$toHost:$dir" "."
  #ssh $toHost "user update.authorized_keys"
}

ossh.key.pull() { # <fromHost> <dir> # gets te directory <dir> <fromHost> into the CURRENT LOCAL dirrectory

  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

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

  ossh.connection.open "$toHost"

  # Resolve keyName: explicit arg → remote ossh id.file.get → remote key auto-detect
  if [ -z "$keyName" ]; then
    keyName=$(ossh exec "$toHost" "ossh id.file.get" 2>/dev/null)
  fi
  if [ -z "$keyName" ]; then
    # Fallback: detect key type directly on remote's ~/.ssh
    keyName=$(ossh exec "$toHost" "for k in id_ed25519 id_ecdsa id_rsa id_dsa; do [ -f ~/.ssh/\$k.pub ] && echo \$k && break; done" 2>/dev/null)
  fi
  if [ -z "$keyName" ]; then
    error.log "key.pull: cannot determine key name on $toHost (no key found in remote ~/.ssh)"
    return 1
  fi
  info.log "key.pull: using keyName='$keyName' from $toHost"

  private.get.sshDir
  local localDir="$RESULT/public_keys"
  [ -d "$localDir" ] || mkdir -p "$localDir"
  private.ossh.rsync.pull "$toHost:.ssh/$keyName.pub" "$localDir/$keyName.public_key.pulled"
}

ossh.config.pull() { # <fromHost> <sshConfigName> <?file> # pulls a config from remote and seves it
  local fromHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no fromHost was specified"
    return 1
  fi

  local sshConfigName="$1"
  if [ -n "$1" ]; then
    shift
  else

    error.log "no sshConfigName was specified."
    important.log "available remote ssh configs:"
    ossh exec $fromHost "ossh config.list"
    return 1
  fi

  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi

  ossh exec $fromHost "ossh config.get $sshConfigName" >>$file
}

ossh.id.pull() # <fromHost> <?file> <?sshDir> # pulls the .ssh dir <formHost> to <sshDir>/ids and renames it
# TODO get remote user and rename to ssh.$remoteUser.$fromHost
{
  local fromHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no fromHost was specified"
    return 1
  fi

  local id=$( ossh exec $fromHost "ossh get.key.name" )

  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  else
    private.get.sshDir
    file="$RESULT/config"
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshDir="$RESULT"

  if ! [ -d $sshDir/ids ]; then
    mkdir -p $sshDir/ids
  fi

  cd $sshDir/ids
  ossh dir.pull $fromHost ".ssh"
  mv .ssh ssh.$id
}

ossh.id.push() # <toHost> <id> <?sshDir> # pushes the <id> <toHost>
{
  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi

  if [ -z "$1" ]; then
    error.log "no id was specified"
    return 1
  else
    idName="$1"
    shift
  fi

  private.get.sshDir "$1"
  [ -n "$1" ] && shift
  local sshDir="$RESULT"
  local idDir="$sshDir/ids/$idName"

  ossh.connection.open "$toHost"
  private.ossh.rsync "$idDir" "$toHost:~/.ssh/ids/"
}

# DEPRECATED — use server.port.get
private.ossh.get.server.port() {
  if [ -f /etc/ssh/sshd_config ]; then
    cat /etc/ssh/sshd_config | line find 'Port ' | line split | line select 2
  else
    echo 22
  fi
}

ossh.server.port.get() { # # get SSH server port from sshd_config
  if [ -f /etc/ssh/sshd_config ]; then
    cat /etc/ssh/sshd_config | line find 'Port ' | line split | line select 2
  else
    echo 22
  fi
}

ossh.server.ip.get() {
  if os.check ossh.server.ip.get; then
    $RESULT "$@"
  else
    important.log "$RESULT is not supported"
  fi  
}

ossh.server.ip.get.darwin() {
  ifconfig | line find 192 | line split | line select 2
}

ossh.server.ip.get.linux() {
  hostname -I
}

ossh.server.config.edit() {
  vim /etc/ssh/sshd_config 
}

ossh.tunnel() # <toHost> <portMap:> # maps the ports on localhost <toHost>
# eg ossh tunnel someHost  8093:127.0.0.1:8080
{
  ssh -o ControlPath="$OSSH_CONTROL_PATH" $1 -L $2
}

ossh.list.open.ports() { # # lists open ports an their applications and users
  lsof -Pn -i4
}

ossh.exec() { # <toHost> <command> # executes the command on host <toHost>

  local toHost="$1"
  if [ -n "$1" ]; then
    shift
  else
    error.log "no toHost was specified"
    return 1
  fi


  ssh -o ControlPath="$OSSH_CONTROL_PATH" $toHost "source config/user.env; $@"
}


ossh.exec.completion() {
  private.ossh.config.complete.hosts "$@"
}

ossh.test() {
  this.isSourced
  echo this.isSourced=$?
  echo "this: $this"
  echo "caller: $caller"
  echo "callerFunction: $callerFunction"
  echo "This: $This"
  #echo "this.this: $(this.this)"
}

# ── DEPRECATED wrappers (verb.object → object.verb) ──────────────────────────

ossh.push.config() { # <toHost> <sshConfigName> # DEPRECATED — use ossh config.push
  ossh.config.push "$@"
}
ossh.pull.config() { # <fromHost> <sshConfigName> <?file> # DEPRECATED — use ossh config.pull
  ossh.config.pull "$@"
}
ossh.push.dir() { # <toHost> <dir> # DEPRECATED — use ossh dir.push
  ossh.dir.push "$@"
}
ossh.pull.dir() { # <fromHost> <dir> # DEPRECATED — use ossh dir.pull
  ossh.dir.pull "$@"
}
ossh.push.key() { # <toHost> <?keyName> <?sshDir:~/.ssh> # DEPRECATED — use ossh key.push
  ossh.key.push "$@"
}
ossh.pull.key() { # <fromHost> <?keyName> # DEPRECATED — use ossh key.pull
  ossh.key.pull "$@"
}
ossh.push.id() { # <toHost> <id> <?sshDir> # DEPRECATED — use ossh id.push
  ossh.id.push "$@"
}
ossh.pull.id() { # <fromHost> <?file> <?sshDir> # DEPRECATED — use ossh id.pull
  ossh.id.pull "$@"
}
private.ossh.get.config() { # <sshConfigHost> <?file:~/.ssh/config> # DEPRECATED — use ossh config.get
  ossh.config.get "$@"
}

# ── GENERIC VERB DISPATCHERS ─────────────────────────────────────────────────
# ossh get <object> → ossh.<object>.get (full dotted path)
# Example: ossh get config.port UpDown.ai → ossh.config.port.get UpDown.ai

private.ossh.dispatch() # <verb> <object> <args...> # find and call ossh.<object>.<verb>
{
  local verb="$1" object="$2"
  shift 2
  # Try: ossh.object.verb → private.ossh.object.verb
  local method="ossh.${object}.${verb}"
  type -t "$method" &>/dev/null && { "$method" "$@"; return $?; }
  method="private.ossh.${object}.${verb}"
  type -t "$method" &>/dev/null && { "$method" "$@"; return $?; }
  error.log "No ${verb} method for '$object' (tried ossh.${object}.${verb})"
  return 1
}

private.ossh.dispatch.objects() # <verb> # list all objects that have .<verb> methods
{
  {
    declare -F | awk '{print $3}' | grep "^ossh\..*\.${1}$" | \
      sed "s/^ossh\.\(.*\)\.${1}$/\1/" | grep -v "^${1}$\|^get$\|^set$\|^list$"
    declare -F | awk '{print $3}' | grep "^private\.ossh\..*\.${1}$" | \
      sed "s/^private\.ossh\.\(.*\)\.${1}$/\1/"
  } | sort -u
}

ossh.get() # <object> <?args...> # dispatch to ossh.<object>.get
{
  local object="$1"
  [ -z "$object" ] && { error.log "Usage: ossh get <object> <?args...>"; ossh.list; return 1; }
  shift
  private.ossh.dispatch get "$object" "$@"
}
ossh.get.completion() { private.ossh.dispatch.objects get; }
ossh.get.completion.object() { private.ossh.dispatch.objects get; }

ossh.set() # <object> <?args...> # dispatch to ossh.<object>.set
{
  local object="$1"
  [ -z "$object" ] && { error.log "Usage: ossh set <object> <?args...>"; return 1; }
  shift
  private.ossh.dispatch set "$object" "$@"
}
ossh.set.completion() { private.ossh.dispatch.objects set; }
ossh.set.completion.object() { private.ossh.dispatch.objects set; }

ossh.list() # <?objectFilter> # list available objects with get/set methods
{
  local filter="${1:-}"
  echo -e "${BOLD_CYAN}Available ossh objects:${NORMAL}"
  { private.ossh.dispatch.objects get; private.ossh.dispatch.objects set; } | \
    sort -u | { [ -n "$filter" ] && grep -i "$filter" || cat; }
}

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

  Usage:
  $this: command   description and Parameter

      usage     prints this dialog while it will print the status when there are no parameters          
      v         print version information
      init      initializes ...nothing yet

      config.get          <sshConfigHost> <?file:\"$HOME/.ssh/config\">   outputs the ssh config

      config.create       <sshConfigHost> <?url> <privateKey:\"$HOME/.ssh/id_rsa\">
      config.show.last    shows \$CONFIG_PATH: $CONFIG_PATH/result.txt
      config.save.last    <?file:\"$HOME/.ssh/config\">

      config.parse.url    <?url> parses a url and shows a resulting config

      install             <?url> installs password free access to the user in the url 

      ----      --------------------------"
  this.help
  echo "${GREEN}
  
  Examples

    $this config.create iMac
    $this config.create dockerSSH test@localhost:8022
    $this config.show.last
    $this config.save.last

  "
}

# ── Filesystem completions for dir/file/sshDir parameters ─────────────────
ossh.dir.push.completion.dir() { compgen -d "$1"; }
ossh.dir.pull.completion.dir() { compgen -d "$1"; }
ossh.key.push.completion.sshDir() { ossh.parameter.completion.sshDir "$@"; }
ossh.key.pull.completion.dir() { compgen -d "$1"; }
ossh.id.pull.completion.sshDir() { ossh.parameter.completion.sshDir "$@"; }
ossh.id.create.completion.sshDir() { ossh.parameter.completion.sshDir "$@"; }

ossh.start()
{
  #echo "sourcing init"
  if [ -z "$CONFIG" ]; then
    CONFIG=$HOME/config/user.env
    source $CONFIG
  fi
  source this
  source line
  source os

  # if [ -z "$1" ]; then
  #   status.discover "$@"
  #   return 0
  # fi

  this.start "$@"
}

ossh.start "$@"

