#!/usr/bin/env bash
# fi.sh — Find Inside SHell engine (Numeric Choice Edition)
# Zero dependencies. Works out-of-the-box on macOS, Linux, and BSD.
#
# Usage: fi.sh [OPTIONS] <search-query>
#
# Options:
#   -a            Search all file types (default: .txt and .md only)
#   -h            Include hidden directories (default: all dot-dirs pruned)
#   -f <path>     Search only this folder (default: entire $HOME tree)
#   -l <n>        Max results to show (default: 100)
#   -help         Show this help

set -euo pipefail

# ── Defaults ─────────────────────────────────────────────────────────────────
DEFAULT_EDITOR="${EDITOR:-nano}"
ALL_FILES=0
HIDDEN=0
SEARCH_ROOT="${HOME}"
LIMIT=100

# ── Colors ────────────────────────────────────────────────────────────────────
if tput colors &>/dev/null && [ "$(tput colors)" -ge 8 ]; then
  CYAN=$(tput setaf 6); GRN=$(tput setaf 2); YLW=$(tput setaf 3)
  RED=$(tput setaf 1); DIM=$(tput setaf 8); BLD=$(tput bold); RST=$(tput sgr0)
else
  CYAN='' GRN='' YLW='' RED='' DIM='' BLD='' RST=''
fi

die()  { printf "${RED}error:${RST} %s\n" "$*" >&2; exit 1; }

show_help() {
  printf "${BLD}><>${RST}\n"
  printf "${BLD}fi.sh${RST} — Find Inside SHell\n\n"
  printf "Usage: fi.sh [OPTIONS] <search-query>\n\n"
  printf "Options:\n"
  printf "  ${BLD}-a${RST}          Search all file types (default: .txt and .md only)\n"
  printf "  ${BLD}-h${RST}          Include hidden directories (default: all dot-dirs pruned)\n"
  printf "  ${BLD}-f${RST} <path>   Search only this folder (default: entire \$HOME tree)\n"
  printf "  ${BLD}-l${RST} <n>      Max results to show (default: 100)\n"
  printf "  ${BLD}-help${RST}       Show this help\n\n"
  printf "Examples:\n"
  printf "  fi.sh budget\n"
  printf "  fi.sh -h quarterly\n"
  printf "  fi.sh -f ~/gn budget meeting\n"
  printf "  fi.sh -a -l 250 quarterly\n"
  printf "  fi.sh -f ~/.do -h tasks\n"
}

# ── Flag Parsing ──────────────────────────────────────────────────────────────
POSITIONAL=()
while [ $# -gt 0 ]; do
  case "$1" in
    -a)
      ALL_FILES=1; shift ;;
    -h)
      HIDDEN=1; shift ;;
    -f)
      [ $# -lt 2 ] && die "-f requires a path argument"
      SEARCH_ROOT="$2"; shift 2 ;;
    -l)
      [ $# -lt 2 ] && die "-l requires a numeric argument"
      [[ "$2" =~ ^[0-9]+$ ]] || die "-l value must be a positive integer"
      LIMIT="$2"; shift 2 ;;
    -help)
      show_help; exit 0 ;;
    --)
      shift; POSITIONAL+=("$@"); break ;;
    -*)
      die "Unknown option: $1 (use -help for usage)" ;;
    *)
      POSITIONAL+=("$1"); shift ;;
  esac
done
set -- "${POSITIONAL[@]:-}"

# ── Validate Search Root ──────────────────────────────────────────────────────
[ -d "$SEARCH_ROOT" ] || die "Search path does not exist: $SEARCH_ROOT"

# ── Ensure Search Term ────────────────────────────────────────────────────────
if [ $# -lt 1 ] || [ -z "${1:-}" ]; then
  show_help
  exit 0
fi
QUERY="$1"

# ── Build AWK Condition ───────────────────────────────────────────────────────
# All words must match (AND logic), each as a case-insensitive substring.
read -r -a query_words <<< "$QUERY"

awk_cond=""
for word in "${query_words[@]}"; do
  clean=$(printf '%s' "$word" \
    | sed 's/\\/\\\\/g; s/\[/\\[/g; s/\]/\\]/g; s/\./\\./g; s/\+/\\+/g
           s/\*/\\*/g; s/\^/\\^/g; s/\$/\\$/g; s/|/\\|/g
           s/(/\\(/g; s/)/\\)/g; s/?/\\?/g' \
    | tr '[:upper:]' '[:lower:]')
  if [ -z "$awk_cond" ]; then
    awk_cond="(tolower(lines[i]) ~ /$clean/)"
  else
    awk_cond="$awk_cond && (tolower(lines[i]) ~ /$clean/)"
  fi
done

# ── Build find Command ────────────────────────────────────────────────────────
# By default all hidden directories (any dir starting with .) are pruned.
# -h disables that pruning so .do, .config, etc. are included.
# Note: find_cmd is kept without a terminal action so callers can append
# -exec or -print as needed.
if [ "$HIDDEN" -eq 1 ]; then
  if [ "$ALL_FILES" -eq 1 ]; then
    find_cmd=(find "$SEARCH_ROOT" -type f)
  else
    find_cmd=(find "$SEARCH_ROOT" -type f \( -name "*.txt" -o -name "*.md" \))
  fi
else
  if [ "$ALL_FILES" -eq 1 ]; then
    find_cmd=(find "$SEARCH_ROOT" \( -name ".*" -prune \) -o \( -type f \))
  else
    find_cmd=(find "$SEARCH_ROOT" \( -name ".*" -prune \) -o \( -type f \( -name "*.txt" -o -name "*.md" \) \))
  fi
fi

# ── AWK: Content Search with Context ─────────────────────────────────────────
# Uses FNR (resets per file) not NR (global), so lines[] stays scoped to one
# file at a time. Called once per file via print0 loop to prevent cross-file
# array bleed and wrong FILENAME in END block.
awk_with_ctx='{lines[FNR]=$0} END { for(i=1;i<=FNR;i++){ if('"$awk_cond"'){ before=(i>1)?lines[i-1]:""; after=(i<FNR)?lines[i+1]:""; gsub(/\n/," ",before); gsub(/\n/," ",after); print "C:" FILENAME ":" i ":" lines[i] "\x01" before "\x01" after } } }'

MATCHES=()

while IFS= read -r -d '' f; do
  while IFS= read -r line; do
    [ -n "$line" ] && MATCHES+=("$line")
  done < <(awk "$awk_with_ctx" "$f" 2>/dev/null)
done < <("${find_cmd[@]}" -print0 2>/dev/null || true)

# ── Filename Matches ──────────────────────────────────────────────────────────
while IFS= read -r fpath; do
  fname_low=$(basename "$fpath" | tr '[:upper:]' '[:lower:]')
  all_match=1
  for word in "${query_words[@]}"; do
    word_low=$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]')
    case "$fname_low" in
      *"$word_low"*) ;;
      *) all_match=0; break ;;
    esac
  done
  if [ "$all_match" -eq 1 ]; then
    MATCHES+=("F:$fpath:0:$(basename "$fpath")"$'\x01'$'\x01')
  fi
done < <("${find_cmd[@]}" -print0 2>/dev/null || true)

# ── Cap Results ───────────────────────────────────────────────────────────────
TOTAL_MATCHES=${#MATCHES[@]}
if [ "$TOTAL_MATCHES" -eq 0 ]; then
  printf "${YLW}No matches found for '%s'.${RST}\n" "$QUERY"
  exit 0
fi

CAPPED=0
if [ "$TOTAL_MATCHES" -gt "$LIMIT" ]; then
  CAPPED=1
  TOTAL_MATCHES="$LIMIT"
fi

# ── TUI ───────────────────────────────────────────────────────────────────────
cleanup() {
  tput cnorm 2>/dev/null || true
  tput rmcup 2>/dev/null || true
}
trap cleanup EXIT INT TERM

tput smcup 2>/dev/null || true
tput clear

TERM_COLS=$(tput cols 2>/dev/null || echo 80)

# Header
printf "${CYAN}${BLD}fi.sh${RST} | "
if [ "$CAPPED" -eq 1 ]; then
  printf "${YLW}showing %d of ${TOTAL_MATCHES}+ results${RST}" "$LIMIT"
  printf "  ${DIM}(use --limit N for more)${RST}"
else
  printf "%d result(s)" "$TOTAL_MATCHES"
fi
printf " for: ${YLW}%s${RST}" "$QUERY"
[ "$ALL_FILES" -eq 1 ]              && printf "  ${DIM}[--all]${RST}"
[ "$HIDDEN" -eq 1 ]                 && printf "  ${DIM}[--hidden]${RST}"
[ "$SEARCH_ROOT" != "${HOME}" ]     && printf "  ${DIM}[--folder %s]${RST}" "$SEARCH_ROOT"
printf "\n"

# Digit width for selection prompt
if [ "$TOTAL_MATCHES" -le 10 ]; then
  DIGIT_WIDTH=1
elif [ "$TOTAL_MATCHES" -le 100 ]; then
  DIGIT_WIDTH=2
else
  DIGIT_WIDTH=3
fi

printf "Type index ${BLD}[0-%d]${RST} to open, ${BLD}q${RST} to quit.\n" $((TOTAL_MATCHES - 1))
printf "═%.0s" $(seq 1 "$TERM_COLS")
printf "\n"

# ── Render Results ────────────────────────────────────────────────────────────
for ((i=0; i<TOTAL_MATCHES; i++)); do
  raw="${MATCHES[i]}"
  IFS=$'\x01' read -r main_part ctx_before ctx_after <<< "$(printf '%b' "$raw")"

  match_type=$(printf '%s' "$main_part" | cut -d':' -f1)
  file_path=$(printf '%s'  "$main_part" | cut -d':' -f2)
  line_num=$(printf '%s'   "$main_part" | cut -d':' -f3)
  content=$(printf '%s'    "$main_part" | cut -d':' -f4-)

  file_name=$(basename "$file_path")
  dir_name=$(basename "$(dirname "$file_path")")

  max_w=$(( TERM_COLS - 24 ))
  [ "$max_w" -lt 20 ] && max_w=20

  if [ "$match_type" = "F" ]; then
    printf " ${GRN}[%0${DIGIT_WIDTH}d]${RST} ${BLD}[%s/%s]${RST} ${CYAN}filename match${RST}\n" \
      "$i" "$dir_name" "$file_name"
  else
    printf " ${GRN}[%0${DIGIT_WIDTH}d]${RST} ${BLD}[%s/%s:%s]${RST} %s\n" \
      "$i" "$dir_name" "$file_name" "$line_num" "${content:0:$max_w}"
    [ -n "$ctx_before" ] && printf "        ${DIM}↑ %s${RST}\n" "${ctx_before:0:$max_w}"
    [ -n "$ctx_after"  ] && printf "        ${DIM}↓ %s${RST}\n" "${ctx_after:0:$max_w}"
  fi
done

printf "\n"

# ── Selection Input ───────────────────────────────────────────────────────────
printf "${CYAN}Selection [0-%d/q]:${RST} " $((TOTAL_MATCHES - 1))

selection_str=""
for (( d=0; d<DIGIT_WIDTH; d++ )); do
  IFS= read -rsn1 ch
  if [ "$d" -eq 0 ] && [ "$ch" = "q" ]; then
    cleanup; exit 0
  fi
  [[ ! "$ch" =~ [0-9] ]] && { cleanup; [ "$d" -gt 0 ] && echo ""; die "Invalid input: digits only."; }
  printf "%s" "$ch"
  selection_str="${selection_str}${ch}"
done
printf "\n"

selection_idx=$(( 10#$selection_str ))
[ "$selection_idx" -ge "$TOTAL_MATCHES" ] && { cleanup; die "Selection out of bounds."; }

cleanup

# ── Launch Editor ─────────────────────────────────────────────────────────────
SELECTED="${MATCHES[selection_idx]}"
IFS=$'\x01' read -r main_part _ _ <<< "$(printf '%b' "$SELECTED")"
TARGET_FILE=$(printf '%s' "$main_part" | cut -d':' -f2)
TARGET_LINE=$(printf '%s' "$main_part" | cut -d':' -f3)

case "$DEFAULT_EDITOR" in
  nano)              exec nano +"$TARGET_LINE" "$TARGET_FILE" ;;
  vim|vi|nvim)       exec "$DEFAULT_EDITOR" +"$TARGET_LINE" "$TARGET_FILE" ;;
  *)                 exec "$DEFAULT_EDITOR" "$TARGET_FILE" ;;
esac
