# =============================================================================
# CUSTOM FUNCTIONS
# =============================================================================

# -----------------------------------------------------------------------------
# Function: funcs (List available .func functions)
# -----------------------------------------------------------------------------
funcs() {
  local func_file="${1:-$HOME/.func}"

  if [ ! -f "$func_file" ]; then
    printf "Error: Function file '%s' not found.\n" "$func_file" >&2
    return 1
  fi

  printf "Available functions in %s:\n" "$func_file"
  awk '
    /^# Function: / {
      line = $0
      sub(/^# Function: /, "", line)

      name = line
      sub(/ .*/, "", name)

      desc = ""
      if (line ~ / \(.+\)$/) {
        desc = line
        sub(/^[^ ]+ \(/, "", desc)
        sub(/\)$/, "", desc)
      }

      if (desc != "") {
        printf "  %-22s %s\n", name, desc
      } else {
        printf "  %s\n", name
      }
    }
  ' "$func_file"
}

# -----------------------------------------------------------------------------
# Function: clip (Cross-platform clipboard)
# -----------------------------------------------------------------------------
clip() {
  local cmd
  local args=()

  if command -v pbcopy >/dev/null 2>&1; then
    cmd="pbcopy" # macOS
  elif grep -qi "microsoft" /proc/version 2>/dev/null && command -v clip.exe >/dev/null 2>&1; then
    cmd="clip.exe" # WSL
  elif [ "$XDG_SESSION_TYPE" = "wayland" ] && command -v wl-copy >/dev/null 2>&1; then
    cmd="wl-copy" # Linux Wayland
  elif command -v xclip >/dev/null 2>&1; then
    cmd="xclip" # Linux X11 (Fallback 1)
    args=("-selection" "clipboard")
  elif command -v xsel >/dev/null 2>&1; then
    cmd="xsel" # Linux X11 (Fallback 2)
    args=("--clipboard" "--input")
  else
    printf "Error: No supported clipboard utility found.\n" >&2
    return 1
  fi

  if [ $# -gt 0 ]; then
    if [ -f "$1" ]; then
      "$cmd" "${args[@]}" < "$1"
      echo "Copied contents of '$1' to clipboard."
    else
      printf "Error: File '%s' not found.\n" "$1" >&2
      return 1
    fi
  else
    "$cmd" "${args[@]}"
  fi
}

# -----------------------------------------------------------------------------
# Function: open_file (Cross-platform file/directory opener)
# -----------------------------------------------------------------------------
open_file() {
    local target="${1:-.}"
    
    if [[ "$OSTYPE" == "darwin"* ]]; then
        command open "$target"
    elif grep -qi "microsoft" /proc/version 2>/dev/null; then
        if command -v wslpath >/dev/null 2>&1 && command -v explorer.exe >/dev/null 2>&1; then
            explorer.exe "$(wslpath -w "$target")"
        else
            printf "Error: 'wslpath' or 'explorer.exe' not found.\n" >&2
            return 1
        fi
    elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
        if command -v xdg-open >/dev/null 2>&1; then
            xdg-open "$target"
        else
            printf "Error: 'xdg-open' not found.\n" >&2
            return 1
        fi
    fi
}
alias open='open_file'

# -----------------------------------------------------------------------------
# Function: clear_history (Supports shell, Claude, Codex, and OpenCode)
# -----------------------------------------------------------------------------
clear_history() {
  case "${1:-}" in
    claude)
      if [ -d "$HOME/.claude/projects" ]; then
        # Use 'yes' to skip prompts and -f to ignore non-existent files
        yes | rm -rf "$HOME/.claude/projects"/*
        echo "Claude project history/cache cleared."
      fi
      ;;

    codex)
      local codex_home="${CODEX_HOME:-$HOME/.codex}"
      local codex_session_file
      local codex_session_id

      mkdir -p "$codex_home"
      : > "$codex_home/history.jsonl"

      if command -v codex >/dev/null 2>&1; then
        find "$codex_home/sessions" "$codex_home/archived_sessions" -type f -name '*.jsonl' -print 2>/dev/null | while IFS= read -r codex_session_file; do
          codex_session_id="$(sed -n 's/.*"session_id":"\([^"]*\)".*/\1/p; q' "$codex_session_file")"
          if [ -n "$codex_session_id" ]; then
            codex delete --force "$codex_session_id" >/dev/null 2>&1 || true
          fi
        done
      fi

      rm -rf "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots"
      mkdir -p "$codex_home/sessions" "$codex_home/archived_sessions" "$codex_home/shell_snapshots"

      echo "Codex history cleared."
      ;;

    opencode)
      local opencode_state_home="${XDG_STATE_HOME:-$HOME/.local/state}/opencode"
      local opencode_data_home="${XDG_DATA_HOME:-$HOME/.local/share}/opencode"

      mkdir -p "$opencode_state_home" "$opencode_data_home"
      : > "$opencode_state_home/prompt-history.jsonl"
      rm -f \
        "$opencode_data_home/opencode.db" \
        "$opencode_data_home/opencode.db-shm" \
        "$opencode_data_home/opencode.db-wal"
      rm -rf "$opencode_data_home/repos" "$opencode_data_home/log"
      mkdir -p "$opencode_data_home/repos" "$opencode_data_home/log"

      echo "OpenCode history cleared."
      ;;

    *)
      # 1. Truncate the file
      : > "$HISTFILE" 

      # 2. Clear RAM by briefly setting history size to 0
      local old_histsize=$HISTSIZE
      HISTSIZE=0
      HISTSIZE=$old_histsize
      
      echo "Shell history cleared."
      ;;
  esac
}

# -----------------------------------------------------------------------------
# Function: require_cli (Check external CLI availability)
# -----------------------------------------------------------------------------
require_cli() {
  local binary="$1"
  local label="$2"
  local resolved

  if command -v whence >/dev/null 2>&1; then
    if whence -p "$binary" >/dev/null 2>&1; then
      return 0
    fi
  else
    resolved="$(command -v "$binary" 2>/dev/null)" || resolved=""
    if [ -n "$resolved" ] && [ -x "$resolved" ]; then
      return 0
    fi
  fi

  printf "Error: %s ('%s') is not installed or not in your PATH.\n" "$label" "$binary" >&2
  return 1
}

# -----------------------------------------------------------------------------
# Function: claude (Includes --yolo and clear shortcuts)
# -----------------------------------------------------------------------------
claude() {
  # Shortcut for clearing cache
  if [[ "$1" == "clear" ]]; then
    clear_history claude
    return 0
  fi

  # Check if binary exists
  require_cli claude "Claude Code CLI" || return 1

  # Handle --yolo mode
  if [[ "$1" == "--yolo" ]]; then
    shift
    command claude --dangerously-skip-permissions "$@"
    return $?
  fi

  command claude "$@"
}

# -----------------------------------------------------------------------------
# Internal: _prompt_confirm_indexing (Confirm project indexing via the terminal)
# -----------------------------------------------------------------------------
_prompt_confirm_indexing() {
  local project_root="$1"
  local reply

  if ! (: </dev/tty) 2>/dev/null; then
    printf "Notice: No interactive terminal available; enhancing without project indexing.\n" >&2
    return 1
  fi

  printf "Allow Auggie to index and use project context from '%s'? (y/N) " "$project_root" >/dev/tty
  if ! IFS= read -r reply </dev/tty; then
    printf "\nNotice: Unable to read confirmation; enhancing without project indexing.\n" >&2
    return 1
  fi

  case "$reply" in
    y|Y|yes|YES|Yes)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

# -----------------------------------------------------------------------------
# Internal: _enhance_prompt (Run Auggie and write only the enhanced prompt)
# -----------------------------------------------------------------------------
_enhance_prompt() {
  local prompt="$1"
  local output_file="$2"
  local run_dir
  local prompt_file
  local workspace
  local cache_dir
  local auth_file
  local log_file
  local parsed_file
  local project_root
  local use_project_context
  local script_pid
  local waited
  local timeout_seconds
  local monitor_status

  if [ -z "$prompt" ]; then
    printf "Error: Prompt was empty.\n" >&2
    return 1
  fi

  if [ ! -s "$HOME/.augment/session.json" ]; then
    printf "Error: Auggie session file not found. Run 'auggie login' first.\n" >&2
    return 1
  fi

  run_dir="$(mktemp -d "${TMPDIR:-/tmp}/auggie-enhance.XXXXXX")" || return 1
  prompt_file="$run_dir/prompt.txt"
  workspace="$run_dir/workspace"
  cache_dir="$run_dir/cache"
  auth_file="$HOME/.augment/session.json"
  log_file="$run_dir/auggie.log"
  parsed_file="$run_dir/enhanced.txt"
  mkdir -p "$workspace" "$cache_dir"
  printf "%s\n" "$prompt" > "$prompt_file"

  project_root="$(git rev-parse --show-toplevel 2>/dev/null)" || project_root="$PWD"
  use_project_context=0
  timeout_seconds=90

  if _prompt_confirm_indexing "$project_root"; then
    use_project_context=1
    timeout_seconds=300
    workspace="$project_root"
    cache_dir="$HOME/.augment"
    printf "Indexing approved; enhancing with project context from '%s'.\n" "$project_root" >&2
  else
    printf "Enhancing without project indexing.\n" >&2
  fi

  (
    if [ "$use_project_context" -eq 1 ]; then
      AUGGIE_PROMPT_FILE="$prompt_file" \
      AUGGIE_WORKSPACE="$workspace" \
      AUGGIE_CACHE_DIR="$cache_dir" \
      AUGGIE_AUTH_FILE="$auth_file" \
        script -q -f -O "$log_file" -c 'auggie --print --enhance-prompt --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --allow-indexing --wait-for-indexing --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE"' </dev/null >/dev/null 2>&1 &
    else
      AUGGIE_PROMPT_FILE="$prompt_file" \
      AUGGIE_WORKSPACE="$workspace" \
      AUGGIE_CACHE_DIR="$cache_dir" \
      AUGGIE_AUTH_FILE="$auth_file" \
        script -q -f -O "$log_file" -c 'auggie --print --enhance-prompt --no-discover-workspaces --workspace-root "$AUGGIE_WORKSPACE" --augment-cache-dir "$AUGGIE_CACHE_DIR" --augment-session-json "$AUGGIE_AUTH_FILE" --dont-save-session --instruction-file "$AUGGIE_PROMPT_FILE"' </dev/null >/dev/null 2>&1 &
    fi

    script_pid=$!
    waited=0

    while kill -0 "$script_pid" >/dev/null 2>&1; do
      if grep -aq "🤖" "$log_file" 2>/dev/null || grep -aq "Tool call:" "$log_file" 2>/dev/null; then
        kill "$script_pid" >/dev/null 2>&1 || true
        wait "$script_pid" >/dev/null 2>&1 || true
        exit 0
      fi

      if [ "$waited" -ge "$timeout_seconds" ]; then
        kill "$script_pid" >/dev/null 2>&1 || true
        wait "$script_pid" >/dev/null 2>&1 || true
        exit 124
      fi

      sleep 1
      waited=$((waited + 1))
    done

    wait "$script_pid" >/dev/null 2>&1 || true
    exit 0
  )
  monitor_status=$?

  if [ "$monitor_status" -eq 124 ]; then
    printf "Error: Timed out waiting for Auggie to enhance the prompt.\n" >&2
    rm -rf "$run_dir"
    return 124
  fi

  perl -ne '
    # Auggie redraws streamed terminal lines with carriage returns. Keep only
    # the final rendered segment so stale text cannot corrupt the prompt.
    s/\r$//;
    s/.*\r//;
    1 while s/[^\x08]\x08//g;
    s/\x08//g;
    s/\e\][^\a]*(?:\a|\e\\)//g;
    s/\e\[[0-?]*[ -\/]*[@-~]//g;
    next if /Script started on/ || /Script done on/;
    if (/^(?:✨\s*)?Enhanced prompt:\s*(.*)$/) {
      $capturing = 1;
      $output .= "$1\n" if length $1;
      next;
    }
    next unless $capturing;
    exit if /^🤖/ || /Tool call:/ || /Session terminated/;
    $output .= $_;
    END {
      $output =~ s/^\s*\n//;
      $output =~ s/\s+\z//;
      print "$output\n" if length $output;
    }
  ' "$log_file" > "$parsed_file"

  if [ ! -s "$parsed_file" ]; then
    printf "Error: Auggie did not return an enhanced prompt.\n" >&2
    rm -rf "$run_dir"
    return 1
  fi

  if ! command cp "$parsed_file" "$output_file"; then
    printf "Error: Unable to save the enhanced prompt.\n" >&2
    rm -rf "$run_dir"
    return 1
  fi
  rm -rf "$run_dir"
}

# -----------------------------------------------------------------------------
# Function: prompt (Enhance a prompt with Auggie and optional project context)
# -----------------------------------------------------------------------------
prompt() {
  local prompt
  local line
  local output_file

  require_cli auggie "Auggie CLI" || return 1
  require_cli script "script utility" || return 1

  if [ $# -gt 0 ]; then
    prompt="$*"
  elif [ ! -t 0 ]; then
    prompt="$(cat)"
  else
    echo "Reading prompt... Type 'EOF' on a new line and press Enter when finished."
    prompt=""

    while IFS= read -r line; do
      [ "$line" = "EOF" ] && break
      if [ -z "$prompt" ]; then
        prompt="$line"
      else
        prompt="${prompt}"$'\n'"${line}"
      fi
    done
  fi

  if [ -z "$prompt" ]; then
    printf "Error: Prompt was empty.\n" >&2
    return 1
  fi

  output_file="$(mktemp "${TMPDIR:-/tmp}/auggie-enhanced-prompt.XXXXXX")" || return 1
  if ! _enhance_prompt "$prompt" "$output_file"; then
    rm -f "$output_file"
    return 1
  fi

  printf "Enhanced prompt:\n"
  command cat "$output_file"
  rm -f "$output_file"

  command auggie account status
}

# -----------------------------------------------------------------------------
# Function: plan_build (Claude Code multi-agent workflow orchestrator)
# -----------------------------------------------------------------------------
plan_build() {
  local claude_args=()
  local use_yolo=0
  local enhance_payload=0

  while [ $# -gt 0 ]; do
    case "$1" in
      --yolo)
        if [ "$use_yolo" -eq 1 ]; then
          echo "Error: Duplicate argument: --yolo"
          echo "Usage: plan_build [--yolo] [--prompt]"
          return 1
        fi
        use_yolo=1
        claude_args=(--yolo)
        ;;
      --prompt)
        if [ "$enhance_payload" -eq 1 ]; then
          echo "Error: Duplicate argument: --prompt"
          echo "Usage: plan_build [--yolo] [--prompt]"
          return 1
        fi
        enhance_payload=1
        ;;
      *)
        echo "Error: Unknown argument: $1"
        echo "Usage: plan_build [--yolo] [--prompt]"
        return 1
        ;;
    esac
    shift
  done

  require_cli claude "Claude Code CLI" || return 1
  require_cli codex "Codex CLI" || return 1
  require_cli coderabbit "CodeRabbit CLI" || return 1
  if [ "$enhance_payload" -eq 1 ]; then
    require_cli auggie "Auggie CLI" || return 1
    require_cli script "script utility" || return 1
  fi

  echo "📥 Reading payload... Type 'EOF' on a new line and press Enter when finished."

  local payload
  local line
  payload=""

  while IFS= read -r line; do
    [ "$line" = "EOF" ] && break
    if [ -z "$payload" ]; then
      payload="$line"
    else
      payload="${payload}"$'\n'"${line}"
    fi
  done

  if [ -z "$payload" ]; then
    echo "❌ Error: Payload was empty."
    return 1
  fi

  if [ "$enhance_payload" -eq 1 ]; then
    local enhanced_file
    enhanced_file="$(mktemp "${TMPDIR:-/tmp}/plan-build-enhanced.XXXXXX")" || return 1

    echo "✨ Enhancing payload with Auggie..."
    if ! _enhance_prompt "$payload" "$enhanced_file"; then
      rm -f "$enhanced_file"
      echo "❌ Error: Prompt enhancement failed; Claude was not launched."
      return 1
    fi

    payload="$(cat "$enhanced_file")"
    rm -f "$enhanced_file"

    if [ -z "$payload" ]; then
      echo "❌ Error: Enhanced payload was empty; Claude was not launched."
      return 1
    fi
  fi

  echo "🚀 Launching Claude Code with your multi-agent workflow..."

  claude "${claude_args[@]}" "Please read \`~/.claude/skills/plan-build/SKILL.md\` and strictly follow the 7-step multi-agent workflow to implement the following task:

$payload"
}

# -----------------------------------------------------------------------------
# Function: aicommit (Automated Conventional Commit Engine Wrapper)
# -----------------------------------------------------------------------------
aicommit() {
  local diff engine
  
  # 1. Get diff safely: exclude lock files/minified files and cap at ~100k characters to prevent CLI crashes
  diff=$(git diff --cached -- . ":(exclude)*.lock" ":(exclude)*-lock.json" ":(exclude)*.min.js" | head -c 100000)
  
  if [ -z "$diff" ]; then
    printf "❌ Error: Nothing staged to commit.\n" >&2
    return 1
  fi

  if [ ! -f "$HOME/.config/ai-commit-prompt.txt" ]; then
    printf "❌ Error: Configuration file not found at ~/.config/ai-commit-prompt.txt\n" >&2
    printf "Please create it and paste your system prompt inside.\n" >&2
    return 1
  fi

  engine=${1:-claude}

  case "$engine" in
    claude)
      echo "🤖 Claude is analyzing staged changes..."
      cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") | command claude -p "Read the provided instructions and diff, then generate and execute the commit."
      ;;

    codex)
      echo "🤖 Codex is analyzing staged changes..."
      cat "$HOME/.config/ai-commit-prompt.txt" <(echo -e "\n\nHere is the git diff:\n$diff") \
        | command codex --ask-for-approval never exec --sandbox workspace-write --ephemeral \
          "Read the provided instructions and diff, then generate and execute the commit."
      ;;

    *)
      printf "❌ Error: Invalid engine '%s'\n" "$engine" >&2
      printf "Usage: aicommit [claude | codex]\n" >&2
      return 1
      ;;
  esac
}

# -----------------------------------------------------------------------------
# Function: create (Efficiently create file + parent dirs)
# -----------------------------------------------------------------------------
create() {
  if ! command -v install >/dev/null 2>&1; then
    printf "Error: 'install' coreutil is not available.\n" >&2
    return 1
  fi

  if [ $# -eq 0 ]; then
    printf "Usage: create <file> [file ...]\n" >&2
    return 2
  fi

  for p in "$@"; do
    # Expand tilde manually if shell doesn't
    [[ "$p" == "~/"* ]] && p="${HOME}/${p#\~/}"

    if [ -z "$p" ] || [ "$p" = "/" ]; then
      printf "create: refusing to operate on '%s'\n" "$p" >&2
      continue
    fi

    if [ -e "$p" ]; then
      printf "create: '%s' already exists. Skipping.\n" "$p" >&2
      continue
    fi

    if install -D /dev/null "$p"; then
      echo "Created '$p'"
    fi
  done
}

# -----------------------------------------------------------------------------
# Function: kill_port (Kill or check the process using a port)
# -----------------------------------------------------------------------------
kill_port() {
  local action="kill"
  local signal="TERM"
  local port
  local pids
  local pid

  case "${1:-}" in
    -c|--check)
      action="check"
      shift
      ;;
    -k|--kill)
      action="kill"
      shift
      ;;
    -f|--force)
      action="kill"
      signal="KILL"
      shift
      ;;
    -h|--help|"")
      printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
      return 1
      ;;
  esac

  port="${1:-}"
  if [[ ! "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
    printf "Error: port must be a number between 1 and 65535.\n" >&2
    printf "Usage: kill_port [--check|-c|--kill|-k|--force|-f] <port_number>\n" >&2
    return 1
  fi

  if [[ "$OSTYPE" == "darwin"* ]]; then
    pids="$(
      {
        lsof -nP -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null
        lsof -nP -tiUDP:"$port" 2>/dev/null
      } | sort -u
    )"

    if [ -z "$pids" ]; then
      printf "Port %s is FREE\n" "$port"
      return 0
    fi

    printf "Port %s is in use:\n" "$port"
    lsof -nP -iTCP:"$port" -sTCP:LISTEN -iUDP:"$port" 2>/dev/null
  else
    local port_hex
    local inodes

    port_hex="$(printf "%04X" "$port")"
    inodes="$(
      awk -v port="$port_hex" '
        NR > 1 {
          split($2, local_addr, ":")
          if (toupper(local_addr[length(local_addr)]) == port && ($4 == "0A" || FILENAME ~ /udp/)) {
            print $10
          }
        }
      ' /proc/net/tcp /proc/net/tcp6 /proc/net/udp /proc/net/udp6 2>/dev/null | sort -u
    )"

    if [ -z "$inodes" ]; then
      printf "Port %s is FREE\n" "$port"
      return 0
    fi

    pids=""
    pids="$(
      find /proc/[0-9]*/fd -maxdepth 1 -type l -printf "%p %l\n" 2>/dev/null |
        awk -v inodes="$inodes" '
          BEGIN {
            split(inodes, inode_list, " ")
            for (i in inode_list) {
              wanted["socket:[" inode_list[i] "]"] = 1
            }
          }
          $2 in wanted {
            split($1, path, "/")
            print path[3]
          }
        ' | sort -u
    )"

    if [ -z "$pids" ]; then
      printf "Port %s is in use, but no readable process owner was found.\n" "$port" >&2
      return 1
    fi

    printf "Port %s is in use:\n" "$port"
    for pid in $pids; do
      if [ -r "/proc/$pid/cmdline" ]; then
        printf "  PID %-8s %s\n" "$pid" "$(tr '\0' ' ' < "/proc/$pid/cmdline")"
      else
        printf "  PID %s\n" "$pid"
      fi
    done
  fi

  if [ "$action" = "kill" ]; then
    for pid in $pids; do
      if kill "-$signal" "$pid" 2>/dev/null; then
        printf "Killed PID %s with SIG%s.\n" "$pid" "$signal"
      else
        printf "Error: failed to kill PID %s. Try with sudo or --force.\n" "$pid" >&2
        return 1
      fi
    done
  fi
}

# -----------------------------------------------------------------------------
# Function: append_path (Adds to session PATH if exists)
# -----------------------------------------------------------------------------
append_path() {
    local dir="$1"
    if [ -d "$dir" ]; then
        case ":$PATH:" in
            *":$dir:"*) ;; 
            *) PATH="$PATH:$dir" ;;
        esac
    elif [ "${SUPPRESS_WARNINGS:-0}" -ne 1 ]; then
        echo "Warning: $dir does not exist." >&2
    fi
}

# -----------------------------------------------------------------------------
# Function: add_path_to_config (Fixed awk/sed injection)
# -----------------------------------------------------------------------------
add_path_to_config() {
  local new_path="$1"
  local config_file="$HOME/.bashrc"
  [[ "$SHELL" == *"zsh"* ]] && config_file="$HOME/.zshrc"

  if [ -z "$new_path" ]; then
    printf "Usage: add_path_to_config <directory>\n" >&2
    return 1
  fi

  new_path="${new_path/#\~/$HOME}"

  if [ ! -d "$new_path" ]; then
    printf "Warning: '%s' does not exist. Add anyway? (y/N) " "$new_path"
    read -r REPLY
    [[ ! $REPLY =~ ^[Yy]$ ]] && return 1
  fi

  # Check if already exists in file
  if grep -Fq "append_path \"$new_path\"" "$config_file"; then
    printf "Notice: Already in %s.\n" "$config_file"
    return 0
  fi

  # Robust Injection: Use sed to insert before export PATH or just append
  if grep -q "^export PATH" "$config_file"; then
    sed -i.bak "/^export PATH/i append_path \"$new_path\"" "$config_file"
  else
    echo "append_path \"$new_path\"" >> "$config_file"
    echo "export PATH" >> "$config_file"
  fi

  printf "✅ Success: Added to %s\n" "$config_file"
  append_path "$new_path" && export PATH
}
