#!/bin/bash

# Watch-build script for automatic rebuilding and restarting
# Monitors source files and runs 'make prod' when changes
# are detected

set -e

usage() {
  echo "Usage: $(basename "$0") [MAKE_COMMAND...]"
  echo "If MAKE_COMMAND is omitted, the default is: 'make clean && make prod'"
  echo "Example:"
  echo "  $(basename "$0") \"make debug\""
  exit 0
}

if [[ "$1" == "-h" || "$1" == "--help" ]]; then
  usage
fi

if [[ $# -gt 0 ]]; then
  MAKE_CMD="$*"
else
  MAKE_CMD="make clean && make prod"
fi

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Process ID of the running program
DEV_PID=""

cleanup() {
  echo -e "\n${YELLOW}Cleaning up...${NC}"

  if [[ -n "$DEV_PID" ]]; then
    echo -e "${YELLOW}Stopping running program (PID: $DEV_PID)${NC}"
    kill $DEV_PID 2>/dev/null || true
    wait $DEV_PID 2>/dev/null || true
  fi

  exit 0
}

# Trap cleanup on script exit
trap cleanup SIGINT SIGTERM EXIT

build_and_run() {
  echo -e "${BLUE}Building project...${NC}"
  echo -e "${BLUE}Running build command: ${YELLOW}$MAKE_CMD${NC}"

  # Stop the currently running program if it exists
  if [[ -n "$DEV_PID" ]]; then
    echo -e "${YELLOW}Stopping running program (PID: $DEV_PID)${NC}"
    kill $DEV_PID 2>/dev/null || true
    wait $DEV_PID 2>/dev/null || true
    DEV_PID=""
  fi

  # Build the project using the provided command and capture output
  TMPOUT=$(mktemp)
  # Record build start time (epoch seconds) to filter processes started during the build
  BUILD_START_TS=$(date +%s)
  if bash -c "$MAKE_CMD" 2>&1 | tee "$TMPOUT"; then
    echo -e "${GREEN}Build successful! Determining output binary...${NC}"

    # Try to find the output file from the last '-o <file>' in build output
    # Use portable awk parsing to avoid non-GNU grep issues on some systems
    OUT_FILE=$(awk '{
      for(i=1;i<=NF;i++){
        if($i=="-o"){ if(i+1<=NF) print $(i+1) }
        else if($i ~ /^-o/){ print substr($i,3) }
      }
    }' "$TMPOUT" | tail -n1)

    # If not found, fall back to newest file in out/
    if [[ -z "$OUT_FILE" ]]; then
      OUT_FILE=$(ls -t out/* 2>/dev/null | head -n1 || true)
    fi

    # Final fallback to previous default
    if [[ -z "$OUT_FILE" ]]; then
      OUT_FILE="./out/prod"
    fi

    # Normalize path (if relative and doesn't start with ./ or /, prefix ./)
    if [[ "$OUT_FILE" != /* && "$OUT_FILE" != ./* ]]; then
      OUT_FILE="./$OUT_FILE"
    fi

    echo -e "${BLUE}Resolved output binary: ${YELLOW}$OUT_FILE${NC}"

    # If the build itself already started the program, detect an existing PID
    EXISTING_PID=""

    # Gather candidate PIDs by matching the basename
    if command -v pgrep &> /dev/null; then
      CANDIDS=$(pgrep -f "$(basename "$OUT_FILE")" || true)
    else
      CANDIDS=$(ps aux | grep "$(basename "$OUT_FILE")" | grep -v grep | awk '{print $2}' || true)
    fi

    if [[ -n "$CANDIDS" ]]; then
      # Resolve the canonical path of the output binary if possible
      if command -v realpath &> /dev/null; then
        REAL_OUT=$(realpath "$OUT_FILE" 2>/dev/null || true)
      else
        REAL_OUT=$(readlink -f "$OUT_FILE" 2>/dev/null || true)
      fi

      for pid in $CANDIDS; do
        # Skip non-numeric entries
        if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
          continue
        fi

        # Check process start time and ignore ones that started before the build
        # Use ps lstart to get human-readable start time and convert to epoch
        PID_LSTART=$(ps -o lstart= -p "$pid" 2>/dev/null || true)
        if [[ -n "$PID_LSTART" ]]; then
          PID_EPOCH=$(date -d "$PID_LSTART" +%s 2>/dev/null || true)
          if [[ -n "$PID_EPOCH" && "$PID_EPOCH" -lt "$BUILD_START_TS" ]]; then
            # process started before build, skip
            continue
          fi
        fi
        # Prefer checking /proc/<pid>/exe for an exact match (Linux)
        if [[ -r "/proc/$pid/exe" ]]; then
          PID_EXE=$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)
          if [[ -n "$PID_EXE" && -n "$REAL_OUT" && "$PID_EXE" = "$REAL_OUT" ]]; then
            EXISTING_PID=$pid
            break
          fi
        fi

        # Fallback: check the process cmdline contains the output path or basename
        if [[ -r "/proc/$pid/cmdline" ]]; then
          CMDLINE=$(tr '\0' ' ' < /proc/$pid/cmdline 2>/dev/null || true)
          if [[ -n "$CMDLINE" && ( "$CMDLINE" = *"$OUT_FILE"* || "$CMDLINE" = *"$(basename "$OUT_FILE")"* ) ]]; then
            EXISTING_PID=$pid
            break
          fi
        fi
      done
    fi

    # if [[ -n "$EXISTING_PID" ]]; then
    #   DEV_PID=$EXISTING_PID
    #   echo -e "${YELLOW}Program already running (PID: $DEV_PID). Reusing it.${NC}"
    # else
      if [[ -x "$OUT_FILE" || -f "$OUT_FILE" ]]; then
        # Start the program in background
        "$OUT_FILE" &
        DEV_PID=$!
        echo -e "${GREEN}Program started with PID: $DEV_PID${NC}"
      else
        echo -e "${RED}Resolved output '$OUT_FILE' does not exist or is not executable.${NC}"
        DEV_PID=""
      fi
    # fi

    # clean up temp
    rm -f "$TMPOUT" || true
  else
    echo -e "${RED}build failed${NC}"
    DEV_PID=""
    rm -f "$TMPOUT" || true
  fi
}

clear_console() {
  if command -v tput &> /dev/null; then
    tput reset
  elif command -v clear &> /dev/null; then
    clear
  else
    printf '\033c'
  fi
}

check_dependencies() {
  if ! command -v inotifywait &> /dev/null; then
    echo -e "${RED}error:${NC} inotifywait is not installed."
    echo -e "Please install ${YELLOW}inotify-tools${NC}"
    exit 1
  fi
}

main() {
  clear_console

  check_dependencies

  echo -e "${GREEN}watching on changes...${NC}"
  echo -e "monitoring: ${BLUE}src/, lib/, makefile${NC}"
  echo ""

  build_and_run

  # Watch for changes
  while true; do
    inotifywait -e modify,create,delete,move \
      --recursive \
      --quiet \
      src/ lib/ makefile 2>/dev/null \
      || {
        echo -e "${RED}error:${NC} Failed to watch directories"
        exit 1
      }

    echo -e "\n${BLUE}file change detected, rebuilding${NC}"

    # Small delay to handle rapid successive changes
    sleep 0.5

    clear_console
    build_and_run
    echo -e "${BLUE}watching for changes${NC}"
  done
}

main
