#!/bin/bash
# Incremental build script - only builds modified Arduino sketches
set -euo pipefail

log() { echo "$@" >&2; }
die() { log "ERROR: $*"; exit 1; }

# ---------- changed files ----------
get_changed_files() {
  # For job-level verification: allow injection
  if [[ -n "${INCREMENTAL_CHANGED_FILES:-}" ]]; then
    printf "%s\n" "${INCREMENTAL_CHANGED_FILES}"
    return 0
  fi

  local result
  if [[ "${CI_PIPELINE_SOURCE:-}" == "merge_request_event" ]]; then
    log "DEBUG: Detecting MR changes (target: origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME})"
    result=$(git diff --name-only "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}..HEAD" 2>/dev/null || true)
  elif [[ -n "${CI_COMMIT_BEFORE_SHA:-}" && "${CI_COMMIT_BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]]; then
    log "DEBUG: Detecting push changes (${CI_COMMIT_BEFORE_SHA}..HEAD)"
    result=$(git diff --name-only "${CI_COMMIT_BEFORE_SHA}..HEAD" 2>/dev/null || true)
  else
    log "DEBUG: Detecting fallback (HEAD~1..HEAD or ls-tree)"
    result=$(git diff --name-only "HEAD~1..HEAD" 2>/dev/null || git ls-tree -r --name-only HEAD 2>/dev/null || true)
  fi
  
  # Ensure we're at project root for path consistency
  if [[ -n "$result" ]]; then
    log "DEBUG: Files changed count: $(echo "$result" | grep -c . || echo 0)"
    echo "$result" | head -5 | sed 's/^/  /'  # Show first 5 files for debugging
  fi
  
  printf "%s\n" "$result"
}

# ---------- rules ----------

# Check if a variants/* change affects a specific board
# Returns: board name if specific board affected, "ALL" if unknown/all boards, empty if not variants change
get_affected_board_from_variants() {
  local f="$1"
  
  # variants/k230_canmv_01studio/* → only affects 01studio
  if [[ "$f" == variants/k230_canmv_01studio/* ]]; then
    echo "01studio"
    return 0
  fi
  
  # variants/k230_canmv_lckfb/* → only affects lckfb
  if [[ "$f" == variants/k230_canmv_lckfb/* ]]; then
    echo "lckfb"
    return 0
  fi
  
  # variants/README.md or other docs → no build needed
  if [[ "$f" == variants/*.md || "$f" == variants/*.txt ]]; then
    echo ""
    return 0
  fi
  
  # Unknown variants file → safe fallback to all boards
  if [[ "$f" == variants/* ]]; then
    echo "ALL"
    return 0
  fi
  
  # Not a variants change
  echo ""
  return 1
}

is_full_build_change() {
  local f="$1"

  # core platform files (NOT variants - handled separately)
  if [[ "$f" == cores/* || "$f" == platform.txt || "$f" == boards.txt ]]; then
    return 0
  fi
  
  # variants changes are handled by get_affected_board_from_variants()
  # Only trigger full build if it affects ALL boards
  if [[ "$f" == variants/* ]]; then
    local board
    board="$(get_affected_board_from_variants "$f")"
    if [[ "$board" == "ALL" ]]; then
      return 0  # Unknown variant → full build for safety
    fi
    return 1  # Specific board or docs → not a full build trigger
  fi

  # library code changes (NOT examples): src/, utility/, headers/sources etc.
  # Anything that changes library implementation should trigger full build because it can break many examples.
  if [[ "$f" == libraries/* ]]; then
    # allow example-only changes to be incremental
    if [[ "$f" == libraries/*/examples/* ]]; then
      return 1
    fi

    # library source-like changes => full build
    if [[ "$f" == libraries/*/src/* || "$f" == libraries/*/utility/* ]]; then
      return 0
    fi

    case "$f" in
      *.h|*.hpp|*.c|*.cc|*.cpp|*.S|*.s|*.a|*.ld|*.mk|CMakeLists.txt)
        return 0
        ;;
    esac
  fi

  return 1
}

# Given a .ino path, return sketch dir if valid (dir name == ino name), else empty
sketch_dir_from_ino() {
  local ino="$1"
  local d n ino_n
  d="$(dirname "$ino")"
  n="$(basename "$d")"
  ino_n="$(basename "$ino" .ino)"
  
  # Debug output
  log "DEBUG sketch_dir_from_ino: ino=$ino"
  log "  d=$d, n=$n, ino_n=$ino_n"
  
  if [[ "$n" == "$ino_n" ]]; then
    log "  → Match! Returning $d"
    echo "$d"
  else
    log "  → NO Match: '$n' != '$ino_n'"
    echo ""
  fi
}

# stdout: either "ALL_SKETCHES" or "ALL_SKETCHES:board" or space-separated sketch dirs
# Also sets AFFECTED_BOARDS global variable
find_modified_sketches() {
  local changed_files="$1"
  local -a sketch_dirs=()
  local -a affected_boards=()

  log "Changed files detected:"
  log "$changed_files"
  log ""

  while IFS= read -r file; do
    [[ -z "$file" ]] && continue

    # Check variants changes first (board-specific)
    if [[ "$file" == variants/* ]]; then
      local board
      board="$(get_affected_board_from_variants "$file")"
      if [[ "$board" == "ALL" ]]; then
        log "Unknown variants change: $file -> request full build (all boards)"
        AFFECTED_BOARDS="ALL"
        echo "ALL_SKETCHES"
        return 0
      elif [[ -n "$board" ]]; then
        log "Variants change for board '$board': $file"
        affected_boards+=("$board")
      else
        log "Variants doc change (skipping): $file"
      fi
      continue
    fi

    # full build triggers (cores, platform.txt, etc.)
    if is_full_build_change "$file"; then
      log "Full-build change detected: $file -> request full build"
      AFFECTED_BOARDS="ALL"
      echo "ALL_SKETCHES"
      return 0
    fi

    # incremental: only .ino sketches (examples/tests)
    if [[ "$file" == *.ino ]]; then
      # only accept sketches under libraries/**/examples or tests/**
      if [[ "$file" == libraries/*/examples/* || "$file" == tests/* ]]; then
        local sd
        sd="$(sketch_dir_from_ino "$file")"
        if [[ -n "$sd" ]]; then
          sketch_dirs+=("$sd")
          log "Modified sketch found: $sd"
        else
          # Debug: why did sketch_dir_from_ino fail?
          local dir_part="$(dirname "$file")"
          local dir_name="$(basename "$dir_part")"
          local ino_name="$(basename "$file" .ino)"
          log "DEBUG: sketch_dir_from_ino returned empty for $file"
          log "  dir_part=$dir_part, dir_name=$dir_name, ino_name=$ino_name"
          log "  Match? dir_name==ino_name -> $dir_name == $ino_name"
          log "Skipping .ino (folder name != ino name): $file"
        fi
      else
        # if someone edits a random .ino elsewhere, treat as full build (safe)
        log "Non-standard .ino change: $file -> request full build"
        AFFECTED_BOARDS="ALL"
        echo "ALL_SKETCHES"
        return 0
      fi
    fi
  done <<< "$changed_files"

  # If we have board-specific variants changes, trigger full build for those boards only
  if [[ ${#affected_boards[@]} -gt 0 ]]; then
    # Deduplicate boards
    local unique_boards
    unique_boards=$(printf "%s\n" "${affected_boards[@]}" | sort -u | tr '\n' ' ' | sed 's/[[:space:]]\+$//')
    AFFECTED_BOARDS="$unique_boards"
    log "Variants changes affect boards: $AFFECTED_BOARDS -> full build for these boards"
    echo "ALL_SKETCHES:$AFFECTED_BOARDS"
    return 0
  fi

  AFFECTED_BOARDS=""
  
  if [[ ${#sketch_dirs[@]} -eq 0 ]]; then
    return 0
  fi

  printf "%s\n" "${sketch_dirs[@]}" | sort -u | tr '\n' ' ' | sed 's/[[:space:]]\+$//'
}

# Return codes:
# 0 ok / nothing
# 1 failed
# 2 full build required (all boards)
# 3 full build required for specific boards (check AFFECTED_BOARDS)
build_specific_sketches() {
  local sketch_dirs="$1"
  local board="$2"
  local fqbn="$3"

  # ALL_SKETCHES → full build for all boards
  if [[ "$sketch_dirs" == "ALL_SKETCHES" ]]; then
    log "Incremental mode: changes require a full build (all boards)."
    return 2
  fi
  
  # ALL_SKETCHES:board1 board2 → full build for specific boards
  if [[ "$sketch_dirs" == ALL_SKETCHES:* ]]; then
    local boards="${sketch_dirs#ALL_SKETCHES:}"
    log "Incremental mode: changes require full build for boards: $boards"
    AFFECTED_BOARDS="$boards"
    return 3
  fi

  log "Starting incremental build..."
  local build_count=0

  for sketch_dir in $sketch_dirs; do
    if [[ -d "${CI_PROJECT_DIR}/${sketch_dir}" ]]; then
      log ""
      log "Building sketch: $sketch_dir"

      local args
      args="-ai ${ARDUINO_IDE_PATH} -au ${ARDUINO_USR_PATH} -b ${board} -fqbn ${fqbn} -s ${CI_PROJECT_DIR}/${sketch_dir}"

      if ! ./sketch_utils.sh build $args; then
        log "Build FAILED: $sketch_dir"
        return 1
      fi

      build_count=$((build_count + 1))
      log "Build OK: $sketch_dir"
    else
      log "WARNING: Sketch directory not found: $sketch_dir"
    fi
  done

  if [[ $build_count -eq 0 ]]; then
    log "No sketches to build. Skipping."
    return 0
  fi

  log ""
  log "Incremental build completed. Built $build_count sketch(es)."
  return 0
}

# Get FQBN for a board name
get_fqbn_for_board() {
  local board="$1"
  case "$board" in
    01studio)
      echo "Kendryte:K230:01studio:debug=off"
      ;;
    lckfb)
      echo "Kendryte:K230:lckfb:debug=off"
      ;;
    *)
      echo "Kendryte:K230:${board}:debug=off"
      ;;
  esac
}

# Build all sketches for a specific board
build_all_for_board() {
  local board="$1"
  local fqbn
  fqbn="$(get_fqbn_for_board "$board")"
  
  log ""
  log "=== Full build for board: $board (FQBN: $fqbn) ==="
  
  if ! ./build-all-sketches.sh -b "$board" -fqbn "$fqbn"; then
    log "Full build FAILED for board: $board"
    return 1
  fi
  
  log "Full build completed for board: $board"
  return 0
}

# ---------------- main ----------------
log "=== Incremental build script start ==="
log "Branch: ${CI_COMMIT_BRANCH:-}"
log "Pipeline source: ${CI_PIPELINE_SOURCE:-}"

[[ -n "${ARDUINO_IDE_PATH:-}" && -n "${ARDUINO_USR_PATH:-}" ]] \
  || die "Missing Arduino env vars: ARDUINO_IDE_PATH / ARDUINO_USR_PATH"

log "Arduino IDE path: ${ARDUINO_IDE_PATH}"
log "Arduino user path: ${ARDUINO_USR_PATH}"
log ""

changed_files="$(get_changed_files)"
if [[ -z "${changed_files//[[:space:]]/}" ]]; then
  log "No file changes detected. Skipping build."
  exit 0
fi

modified_sketches="$(find_modified_sketches "$changed_files")"
if [[ -z "${modified_sketches//[[:space:]]/}" ]]; then
  log "No sketches detected for incremental build. Done."
  exit 0
fi

# Default board for incremental builds
BOARD="01studio"
FQBN="Kendryte:K230:01studio:debug=off"

log ""
log "Build configuration:"
log "  Default Board: ${BOARD}"
log "  Default FQBN: ${FQBN}"
log ""

set +e
build_specific_sketches "$modified_sketches" "$BOARD" "$FQBN"
rc=$?
set -e

case $rc in
  0)
    log "Incremental build finished successfully."
    echo "BOARDS_TO_BUILD="
    ;;
  1)
    die "Incremental build failed."
    ;;
  2)
    # Full build required for ALL boards
    log "Changes require full build for all boards."
    log ""
    
    # Output the boards that need building
    echo "BOARDS_TO_BUILD=01studio lckfb"
    
    all_boards="01studio lckfb"
    failed_boards=""
    
    for board in $all_boards; do
      if ! build_all_for_board "$board"; then
        failed_boards="$failed_boards $board"
      fi
    done
    
    if [[ -n "$failed_boards" ]]; then
      die "Full build failed for boards:$failed_boards"
    fi
    
    log ""
    log "Full build completed for all boards."
    ;;
  3)
    # Full build required for specific boards only (variants changes)
    log "Changes require full build for specific boards: $AFFECTED_BOARDS"
    log ""
    
    # Output the boards that need building
    echo "BOARDS_TO_BUILD=$AFFECTED_BOARDS"
    
    failed_boards=""
    
    for board in $AFFECTED_BOARDS; do
      if ! build_all_for_board "$board"; then
        failed_boards="$failed_boards $board"
      fi
    done
    
    if [[ -n "$failed_boards" ]]; then
      die "Full build failed for boards:$failed_boards"
    fi
    
    log ""
    log "Full build completed for affected boards: $AFFECTED_BOARDS"
    ;;
  *)
    die "Unknown return code: $rc"
    ;;
esac

log "=== Incremental build script end ==="
