#!/usr/bin/env bash
# Shared Agent Utilities - Minimal, focused helper functions
set -euo pipefail

#######################
## PROJECT DETECTION ##
#######################

# Cache file name for detected project info
_COYOTE_PROJECT_CACHE=".coyote-project.json"

# Read cached project detection if valid
# Usage: _read_project_cache "/path/to/project"
# Returns: cached JSON on stdout (exit 0) or nothing (exit 1)
_read_project_cache() {
  local dir="$1"
  local cache_file="${dir}/${_COYOTE_PROJECT_CACHE}"

  if [[ -f "${cache_file}" ]]; then
    local cached
    cached=$(cat "${cache_file}" 2>/dev/null) || return 1
    if echo "${cached}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
      echo "${cached}"
      return 0
    fi
  fi
  return 1
}

# Write project detection result to cache
# Usage: _write_project_cache "/path/to/project" '{"type":"rust",...}'
_write_project_cache() {
  local dir="$1"
  local json="$2"
  local cache_file="${dir}/${_COYOTE_PROJECT_CACHE}"

  echo "${json}" > "${cache_file}" 2>/dev/null || true
}

_detect_heuristic() {
  local dir="$1"

  # Rust
  if [[ -f "${dir}/Cargo.toml" ]]; then
    echo '{"type":"rust","build":"cargo build","test":"cargo test","check":"cargo check"}'
    return 0
  fi

  # Go
  if [[ -f "${dir}/go.mod" ]]; then
    echo '{"type":"go","build":"go build ./...","test":"go test ./...","check":"go vet ./..."}'
    return 0
  fi

  # Node.JS/Deno/Bun
  if [[ -f "${dir}/deno.json" ]] || [[ -f "${dir}/deno.jsonc" ]]; then
    echo '{"type":"deno","build":"deno task build","test":"deno test","check":"deno lint"}'
    return 0
  fi

  if [[ -f "${dir}/package.json" ]]; then
    local pm="npm"

    [[ -f "${dir}/bun.lockb" ]] || [[ -f "${dir}/bun.lock" ]] && pm="bun"
    [[ -f "${dir}/pnpm-lock.yaml" ]] && pm="pnpm"
    [[ -f "${dir}/yarn.lock" ]] && pm="yarn"

    echo "{\"type\":\"nodejs\",\"build\":\"${pm} run build\",\"test\":\"${pm} test\",\"check\":\"${pm} run lint\"}"
    return 0
  fi

  # Python
  if [[ -f "${dir}/pyproject.toml" ]] || [[ -f "${dir}/setup.py" ]] || [[ -f "${dir}/setup.cfg" ]]; then
    local test_cmd="pytest"
    local check_cmd="ruff check ."

    if [[ -f "${dir}/poetry.lock" ]]; then
      test_cmd="poetry run pytest"
      check_cmd="poetry run ruff check ."
    elif [[ -f "${dir}/uv.lock" ]]; then
      test_cmd="uv run pytest"
      check_cmd="uv run ruff check ."
    fi

    echo "{\"type\":\"python\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"${check_cmd}\"}"
    return 0
  fi

  # JVM (Maven)
  if [[ -f "${dir}/pom.xml" ]]; then
    echo '{"type":"java","build":"mvn compile","test":"mvn test","check":"mvn verify"}'
    return 0
  fi

  # JVM (Gradle)
  if [[ -f "${dir}/build.gradle" ]] || [[ -f "${dir}/build.gradle.kts" ]]; then
    local gw="gradle"
    [[ -f "${dir}/gradlew" ]] && gw="./gradlew"
    echo "{\"type\":\"java\",\"build\":\"${gw} build\",\"test\":\"${gw} test\",\"check\":\"${gw} check\"}"
    return 0
  fi

  # .NET / C#
  if compgen -G "${dir}/*.sln" &>/dev/null || compgen -G "${dir}/*.csproj" &>/dev/null; then
    echo '{"type":"dotnet","build":"dotnet build","test":"dotnet test","check":"dotnet build --warnaserrors"}'
    return 0
  fi

  # C/C++ (CMake)
  if [[ -f "${dir}/CMakeLists.txt" ]]; then
    echo '{"type":"cmake","build":"cmake --build build","test":"ctest --test-dir build","check":"cmake --build build"}'
    return 0
  fi

  # Ruby
  if [[ -f "${dir}/Gemfile" ]]; then
    local test_cmd="bundle exec rake test"
    [[ -f "${dir}/Rakefile" ]] && grep -q "rspec" "${dir}/Gemfile" 2>/dev/null && test_cmd="bundle exec rspec"
    echo "{\"type\":\"ruby\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"bundle exec rubocop\"}"
    return 0
  fi

  # Elixir
  if [[ -f "${dir}/mix.exs" ]]; then
    echo '{"type":"elixir","build":"mix compile","test":"mix test","check":"mix credo"}'
    return 0
  fi

  # PHP
  if [[ -f "${dir}/composer.json" ]]; then
    echo '{"type":"php","build":"","test":"./vendor/bin/phpunit","check":"./vendor/bin/phpstan analyse"}'
    return 0
  fi

  # Swift
  if [[ -f "${dir}/Package.swift" ]]; then
    echo '{"type":"swift","build":"swift build","test":"swift test","check":"swift build"}'
    return 0
  fi

  # Zig
  if [[ -f "${dir}/build.zig" ]]; then
    echo '{"type":"zig","build":"zig build","test":"zig build test","check":"zig build"}'
    return 0
  fi

  # Generic build systems (last resort before LLM)
  if [[ -f "${dir}/justfile" ]] || [[ -f "${dir}/Justfile" ]]; then
    echo '{"type":"just","build":"just build","test":"just test","check":"just lint"}'
    return 0
  fi

  if [[ -f "${dir}/Makefile" ]] || [[ -f "${dir}/makefile" ]] || [[ -f "${dir}/GNUmakefile" ]]; then
    echo '{"type":"make","build":"make build","test":"make test","check":"make lint"}'
    return 0
  fi

  return 1
}

# Gather lightweight evidence about a project for LLM analysis
# Usage: _gather_project_evidence "/path/to/project"
# Returns: evidence string on stdout
_gather_project_evidence() {
  local dir="$1"
  local evidence=""

  evidence+="Root files and directories:"$'\n'
  evidence+=$(ls -1 "${dir}" 2>/dev/null | head -50)
  evidence+=$'\n\n'

  evidence+="File extension counts:"$'\n'
  evidence+=$(find "${dir}" -type f \
    -not -path '*/.git/*' \
    -not -path '*/node_modules/*' \
    -not -path '*/target/*' \
    -not -path '*/dist/*' \
    -not -path '*/__pycache__/*' \
    -not -path '*/vendor/*' \
    -not -path '*/.build/*' \
    2>/dev/null \
    | sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10)
  evidence+=$'\n\n'

  local config_patterns=("*.toml" "*.yaml" "*.yml" "*.json" "*.xml" "*.gradle" "*.gradle.kts" "*.cabal" "*.pro" "Makefile" "justfile" "Justfile" "Dockerfile" "Taskfile*" "BUILD" "WORKSPACE" "flake.nix" "shell.nix" "default.nix")
  local found_configs=0

  for pattern in "${config_patterns[@]}"; do
    if [[ ${found_configs} -ge 5 ]]; then
      break
    fi

    local files
    files=$(find "${dir}" -maxdepth 1 -name "${pattern}" -type f 2>/dev/null)

    while IFS= read -r f; do
      if [[ -n "${f}" && ${found_configs} -lt 5 ]]; then
        local basename
        basename=$(basename "${f}")
        evidence+="--- ${basename} (first 30 lines) ---"$'\n'
        evidence+=$(head -30 "${f}" 2>/dev/null)
        evidence+=$'\n\n'
        found_configs=$((found_configs + 1))
      fi
    done <<< "${files}"
  done

  echo "${evidence}"
}

# LLM-based project detection fallback
# Usage: _detect_with_llm "/path/to/project"
# Returns: JSON on stdout or empty (exit 1)
_detect_with_llm() {
  local dir="$1"
  local evidence
  evidence=$(_gather_project_evidence "${dir}")
  local prompt
  prompt=$(cat <<-EOF

		Analyze this project directory and determine the project type, primary language, and the correct shell commands to build, test, and check (lint/typecheck) it.

		EOF
	)
  prompt+=$'\n'"${evidence}"$'\n'
  prompt+=$(cat <<-EOF

		Respond with ONLY a valid JSON object. No markdown fences, no explanation, no extra text.
		The JSON must have exactly these 4 keys:
		{"type":"<language>","build":"<build command>","test":"<test command>","check":"<lint or typecheck command>"}

		Rules:
		- "type" must be a single lowercase word (e.g. rust, go, python, nodejs, java, ruby, elixir, cpp, c, zig, haskell, scala, kotlin, dart, swift, php, dotnet, etc.)
		- If a command doesn't apply to this project, use an empty string, ""
		- Use the most standard/common commands for the detected ecosystem
		- If you detect a package manager lockfile, use that package manager (e.g. pnpm over npm)
		EOF
	)

  local llm_response
  llm_response=$(coyote --no-stream "${prompt}" 2>/dev/null) || return 1

  llm_response=$(echo "${llm_response}" | sed 's/^```json//;s/^```//;s/```$//' | tr -d '\n' | sed 's/^[[:space:]]*//')
  llm_response=$(echo "${llm_response}" | grep -o '{[^}]*}' | head -1)

  if echo "${llm_response}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
    echo "${llm_response}" | jq -c '{type: (.type // "unknown"), build: (.build // ""), test: (.test // ""), check: (.check // "")}'
    return 0
  fi

  return 1
}

# Detect project type and return build/test commands
# Uses: cached result -> fast heuristics -> LLM fallback
detect_project() {
  local dir="${1:-.}"

  local cached
  if cached=$(_read_project_cache "${dir}"); then
    echo "${cached}" | jq -c '{type, build, test, check}'
    return 0
  fi

  local result
  if result=$(_detect_heuristic "${dir}"); then
    local enriched
    enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"heuristic","_cached_at":"'"$(date -Iseconds)"'"}')

    _write_project_cache "${dir}" "${enriched}"

    echo "${result}"
    return 0
  fi

  if result=$(_detect_with_llm "${dir}"); then
    local enriched
    enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"llm","_cached_at":"'"$(date -Iseconds)"'"}')

    _write_project_cache "${dir}" "${enriched}"

    echo "${result}"
    return 0
  fi

  echo '{"type":"unknown","build":"","test":"","check":""}'
}

###########################
## FILE SEARCH UTILITIES ##
###########################

_search_files() {
  local pattern="$1"
  local dir="${2:-.}"
  
  find "${dir}" -type f -name "${pattern}" \
    -not -path '*/target/*' \
    -not -path '*/node_modules/*' \
    -not -path '*/.git/*' \
    -not -path '*/dist/*' \
    -not -path '*/__pycache__/*' \
    2>/dev/null | head -25
}

get_tree() {
  local dir="${1:-.}"
  local depth="${2:-3}"
  
  if command -v tree &>/dev/null; then
    tree -L "${depth}" --noreport -I 'node_modules|target|dist|.git|__pycache__|*.pyc' "${dir}" 2>/dev/null || find "${dir}" -maxdepth "${depth}" -type f | head -50
  else
    find "${dir}" -maxdepth "${depth}" -type f \
      -not -path '*/target/*' \
      -not -path '*/node_modules/*' \
      -not -path '*/.git/*' \
      2>/dev/null | head -50
  fi
}
