#!/bin/bash
# path-utils.sh
# Cross-platform path utility functions for Windows/Mac/Linux compatibility.
#
# Usage:
#   source "${SCRIPT_DIR}/path-utils.sh"
#   detect_os
#   is_absolute_path "/some/path"
#   normalize_path "C:\Users\name"
#
# Supports:
#   - macOS (darwin)
#   - Linux
#   - Windows (Git Bash/MSYS2/Cygwin/WSL)

# ============================================================================
# OS Detection
# ============================================================================

# Cache variable for detect_os result (set on first call)
_DETECTED_OS=""

# Detect the current operating system
# Returns: darwin, linux, windows, or unknown
# Note: Result is cached for performance
detect_os() {
  # Return cached result if available
  if [ -n "$_DETECTED_OS" ]; then
    echo "$_DETECTED_OS"
    return 0
  fi

  local result
  case "${OSTYPE:-}" in
    darwin*) result="darwin" ;;
    linux*)
      # Check for WSL
      if grep -qiE "(microsoft|wsl)" /proc/version 2>/dev/null; then
        result="windows"
      else
        result="linux"
      fi
      ;;
    msys*|cygwin*|mingw*)
      result="windows"
      ;;
    *)
      # Fallback: check for Windows environment variables
      if [ -n "${WINDIR:-}" ] || [ -n "${SYSTEMROOT:-}" ]; then
        result="windows"
      elif [ "$(uname -s 2>/dev/null)" = "Darwin" ]; then
        result="darwin"
      elif [ "$(uname -s 2>/dev/null)" = "Linux" ]; then
        result="linux"
      else
        result="unknown"
      fi
      ;;
  esac

  # Cache and return the result
  _DETECTED_OS="$result"
  echo "$result"
}

# Check if running on Windows (any variant)
is_windows() {
  [ "$(detect_os)" = "windows" ]
}

# Check if running on macOS
is_macos() {
  [ "$(detect_os)" = "darwin" ]
}

# Check if running on Linux (excluding WSL)
is_linux() {
  [ "$(detect_os)" = "linux" ]
}

# ============================================================================
# Path Detection
# ============================================================================

# Check if a path is absolute (supports Unix and Windows formats)
# Examples:
#   /home/user       -> true (Unix)
#   C:/Users/name    -> true (Windows with forward slash)
#   C:\Users\name    -> true (Windows with backslash)
#   D:\              -> true (Windows drive root)
#   ./relative       -> false
#   relative/path    -> false
is_absolute_path() {
  local p="$1"
  [ -z "$p" ] && return 1

  # Unix absolute path (starts with /)
  [[ "$p" == /* ]] && return 0

  # Windows absolute path (drive letter: C:/ or C:\)
  # Also handles uppercase and lowercase drive letters
  [[ "$p" =~ ^[A-Za-z]:[\\/] ]] && return 0

  # Windows UNC path (\\server\share or //server/share)
  [[ "$p" =~ ^[\\/][\\/] ]] && return 0

  return 1
}

# Check if a path is a Windows-style path
is_windows_path() {
  local p="$1"
  [ -z "$p" ] && return 1

  # Drive letter format
  [[ "$p" =~ ^[A-Za-z]:[\\/] ]] && return 0

  # UNC path
  [[ "$p" =~ ^[\\/][\\/] ]] && return 0

  return 1
}

# ============================================================================
# Path Normalization
# ============================================================================

# Normalize a path:
#   - Convert backslashes to forward slashes
#   - Remove trailing slashes (except for root paths)
#   - Collapse multiple slashes
# Note: Does NOT resolve symlinks or relative components (use realpath for that)
normalize_path() {
  local p="$1"
  [ -z "$p" ] && echo "" && return 0

  # Convert backslashes to forward slashes using parameter expansion
  # This is much faster than character-by-character loop
  p="${p//\\//}"

  # Collapse multiple slashes to single slash using tr -s (squeeze repeats)
  # This is more reliable than parameter expansion for this operation
  if [[ "$p" == //* ]]; then
    # Preserve leading // for UNC paths
    local prefix="//"
    p="${p#//}"
    p="$(printf '%s' "$p" | tr -s '/')"
    p="${prefix}${p}"
  else
    p="$(printf '%s' "$p" | tr -s '/')"
  fi

  # Remove trailing slash (unless it's root "/" or "C:/")
  if [ "${#p}" -gt 1 ]; then
    # Check if it's a Windows drive root (e.g., "C:/")
    if [[ "$p" =~ ^[A-Za-z]:/$ ]]; then
      : # Keep the trailing slash for drive roots
    else
      p="${p%/}"
    fi
  fi

  echo "$p"
}

# Convert path to native format for the current OS
# On Windows Git Bash: converts to Windows path if needed
# On Unix: returns the normalized path as-is
to_native_path() {
  local p="$1"
  [ -z "$p" ] && echo "" && return 0

  p="$(normalize_path "$p")"

  if is_windows; then
    # In Git Bash/MSYS2, we typically want Unix-style paths
    # But if the caller needs a Windows path, they should use cygpath
    if command -v cygpath >/dev/null 2>&1; then
      cygpath -u "$p" 2>/dev/null || echo "$p"
    else
      echo "$p"
    fi
  else
    echo "$p"
  fi
}

# Convert path to Windows format (if on Windows)
to_windows_path() {
  local p="$1"
  [ -z "$p" ] && echo "" && return 0

  if is_windows && command -v cygpath >/dev/null 2>&1; then
    cygpath -w "$p" 2>/dev/null || echo "$p"
  else
    echo "$p"
  fi
}

# ============================================================================
# Path Comparison
# ============================================================================

# Compare two paths for equality after normalization
# Returns 0 (true) if paths are equivalent, 1 (false) otherwise
paths_equal() {
  local p1="$1"
  local p2="$2"

  p1="$(normalize_path "$p1")"
  p2="$(normalize_path "$p2")"

  # Case-insensitive comparison on Windows
  if is_windows; then
    [[ "${p1,,}" == "${p2,,}" ]]
  else
    [ "$p1" = "$p2" ]
  fi
}

# Check if path1 is under path2 (path2 is a parent of path1)
# Example: is_path_under "/home/user/project/file.txt" "/home/user" -> true
is_path_under() {
  local child="$1"
  local parent="$2"

  child="$(normalize_path "$child")"
  parent="$(normalize_path "$parent")"

  # Ensure parent ends with / for comparison
  [[ "$parent" != */ ]] && parent="${parent}/"

  if is_windows; then
    [[ "${child,,}/" == "${parent,,}"* ]] || [[ "${child,,}" == "${parent%/}" ]]
  else
    [[ "${child}/" == "${parent}"* ]] || [ "$child" = "${parent%/}" ]
  fi
}

# ============================================================================
# Utility Functions
# ============================================================================

# Get the directory portion of a path (cross-platform dirname)
get_dirname() {
  local p="$1"
  p="$(normalize_path "$p")"

  # Use bash parameter expansion for portability
  local dir="${p%/*}"

  # Handle edge cases
  if [ "$dir" = "$p" ]; then
    # No slash found - current directory
    echo "."
  elif [ -z "$dir" ]; then
    # Path was just "/something" - root
    echo "/"
  else
    echo "$dir"
  fi
}

# Get the filename portion of a path (cross-platform basename)
get_basename() {
  local p="$1"
  p="$(normalize_path "$p")"

  # Remove trailing slash first
  p="${p%/}"

  # Get the last component
  echo "${p##*/}"
}

# Get file extension (without the dot)
get_extension() {
  local p="$1"
  local filename
  filename="$(get_basename "$p")"

  # Check if there's a dot (and it's not at the beginning)
  if [[ "$filename" == *.* ]] && [[ "$filename" != .* ]]; then
    echo "${filename##*.}"
  else
    echo ""
  fi
}

# ============================================================================
# sed Compatibility
# ============================================================================

# Cross-platform sed in-place editing
# Usage: sed_inplace 's/old/new/' file.txt
sed_inplace() {
  local expr="$1"
  local file="$2"

  if is_macos; then
    sed -i '' "$expr" "$file"
  else
    sed -i "$expr" "$file"
  fi
}

# ============================================================================
# Export Functions (for sourcing)
# ============================================================================

# Make functions available when sourced
export -f detect_os is_windows is_macos is_linux 2>/dev/null || true
export -f is_absolute_path is_windows_path 2>/dev/null || true
export -f normalize_path to_native_path to_windows_path 2>/dev/null || true
export -f paths_equal is_path_under 2>/dev/null || true
export -f get_dirname get_basename get_extension 2>/dev/null || true
export -f sed_inplace 2>/dev/null || true