#!/usr/bin/env bash
# Author: OpenAI Codex
# AI-assisted implementation, generated and reviewed with OpenAI Codex.

set -euo pipefail
# Keep glob expansion and sorting stable across locales, so -n remains
# repeatable when the service and filesystem contents have not changed.
scf_lc_all_was_set=false
scf_original_lc_all=""
if [[ -n ${LC_ALL+x} ]]; then
  scf_lc_all_was_set=true
  scf_original_lc_all=$LC_ALL
fi
export LC_ALL=C

restore_locale() {
  if [[ $scf_lc_all_was_set == true ]]; then
    export LC_ALL=$scf_original_lc_all
  else
    unset LC_ALL
  fi
}

usage() {
  cat >&2 <<'EOF'
Usage: scf [-r] [-n NUM] SERVICE

Open the configuration file referenced by a systemd service's ExecStart.
SERVICE may be given with or without the .service suffix.

  -r  recursively inspect referenced files and directories (maximum reference depth: 3)
  -n  open candidate NUM directly, without displaying the selection list

Environment:
  SCF_MAX_CANDIDATES  maximum number of candidates (default: 20)
  SCF_MAX_DEPTH       maximum reference depth (default: 3)
EOF
  exit 2
}

recursive=false
selection_number=""
service=""
while (( $# > 0 )); do
  argument=$1
  shift

  if [[ $argument == -- ]]; then
    [[ -z $service && $# -eq 1 ]] || usage
    service=$1
    shift
    break
  fi

  if [[ $argument == -* ]]; then
    options=${argument#-}
    [[ -n $options ]] || usage
    while [[ -n $options ]]; do
      option=${options:0:1}
      options=${options:1}
      case $option in
        r) recursive=true ;;
        n)
          if [[ -n $options ]]; then
            selection_number=$options
            options=""
          else
            (( $# > 0 )) || usage
            selection_number=$1
            shift
          fi
          ;;
        *) usage ;;
      esac
    done
  else
    [[ -z $service ]] || usage
    service=$argument
  fi
done

if [[ -z $service ]]; then
  usage
fi

interactive=false
if [[ -t 0 && -t 1 && -t 2 ]]; then
  interactive=true
fi

# Prompts and candidate details are written to stderr.  Keep pipelines and
# redirected output free of escape sequences.
if [[ $interactive == true ]]; then
  color_item=$'\033[1;36m'
  color_detail=$'\033[2;37m'
  color_prompt=$'\033[1;33m'
  color_reset=$'\033[0m'
else
  color_item=""
  color_detail=""
  color_prompt=""
  color_reset=""
fi

if [[ -n $selection_number && ! $selection_number =~ ^[0-9]+$ ]]; then
  echo "scf: invalid candidate number: $selection_number" >&2
  exit 2
fi
if [[ -n $selection_number ]]; then
  # Bash arithmetic treats numbers with a leading zero as octal.
  selection_number=$((10#$selection_number))
  if (( selection_number < 1 )); then
    echo "scf: invalid candidate number: 0" >&2
    exit 2
  fi
fi

service_argument=$service
if [[ $service != *.service ]]; then
  service+=.service
fi

# Keep the argument a unit name, rather than allowing it to become a
# systemctl option.  This also makes accidental shell syntax harmless.
if [[ ! $service =~ ^[A-Za-z0-9_.@:%+-]+\.service$ ]]; then
  echo "scf: invalid service name: $service_argument" >&2
  exit 2
fi

if ! exec_start=$(systemctl show --no-pager --property=ExecStart --value "$service"); then
  echo "scf: cannot inspect $service" >&2
  exit 1
fi

if [[ -z $exec_start || $exec_start == "{}" ]]; then
  echo "scf: $service has no ExecStart" >&2
  exit 1
fi

# Keep recursion bounded even when configuration files refer to each other.
max_candidates=${SCF_MAX_CANDIDATES:-20}
max_reference_depth=${SCF_MAX_DEPTH:-3}
if [[ ! $max_candidates =~ ^[0-9]+$ ]]; then
  echo "scf: SCF_MAX_CANDIDATES must be a positive integer" >&2
  exit 2
fi
if [[ ! $max_reference_depth =~ ^[0-9]+$ ]]; then
  echo "scf: SCF_MAX_DEPTH must be a non-negative integer" >&2
  exit 2
fi
# Normalize user-provided decimal values before using Bash arithmetic.
max_candidates=$((10#$max_candidates))
max_reference_depth=$((10#$max_reference_depth))
if (( max_candidates < 1 )); then
  echo "scf: SCF_MAX_CANDIDATES must be a positive integer" >&2
  exit 2
fi
declare -A candidate_seen=()
declare -A candidate_depth=()
declare -A candidate_source=()
declare -A candidate_source_detail=()
config_paths=()
other_paths=()
recursive_paths=()
recursive_depths=()
declare -A recursive_seen=()

path_pattern="/[^[:space:];,'\"})]+"

extract_paths() {
  grep -a -oE "$path_pattern" || true
}

has_config_extension() {
  local path=$1
  [[ $path =~ \.(conf|cfg|cnf|ini|json|toml|ya?ml|xml|properties)$ ]]
}

is_config_path() {
  local path=$1
  has_config_extension "$path" \
    || [[ $path == *config* || $path == *configuration* ]]
}

add_candidate() {
  local path=$1
  local depth=$2
  local source=$3
  local source_detail=${4:-}

  [[ -f $path ]] || return 1
  [[ -n ${candidate_seen[$path]+seen} ]] && return 0
  (( ${#candidate_seen[@]} < max_candidates )) || return 1

  candidate_seen[$path]=1
  candidate_depth[$path]=$depth
  candidate_source[$path]=$source
  candidate_source_detail[$path]=$source_detail
  if is_config_path "$path"; then
    config_paths+=("$path")
  else
    other_paths+=("$path")
  fi
}

enqueue_recursive() {
  local path=$1
  local depth=$2

  [[ $recursive == true ]] || return 0
  (( depth <= max_reference_depth )) || return 0
  [[ -f $path ]] || return 0
  [[ -n ${recursive_seen[$path]+seen} ]] && return 0

  recursive_seen[$path]=1
  recursive_paths+=("$path")
  recursive_depths+=("$depth")
}

expand_directory() {
  local directory=$1
  local depth=$2
  local source=$3
  local path
  local entries

  [[ $recursive == true && -d $directory ]] || return 0

  # dotglob also catches hidden configuration files.  nullglob prevents the
  # literal pattern from being treated as a candidate for an empty directory.
  shopt -s dotglob nullglob
  entries=("$directory"/*)
  shopt -u dotglob nullglob

  for path in "${entries[@]}"; do
    # A service's configuration often contains runtime directories such as
    # Python's bin directory.  Only configuration-looking files from a
    # directory should consume the candidate budget.
    has_config_extension "$path" || continue
    add_candidate "$path" "$depth" "$source" "directory $directory" || continue
    enqueue_recursive "$path" "$depth"
  done
}

consider_initial_path() {
  local path=$1

  if [[ -f $path ]]; then
    add_candidate "$path" 0 "ExecStart" || true
  elif [[ -d $path ]]; then
    expand_directory "$path" 0 "ExecStart"
  fi
}

# systemctl's ExecStart value contains both `path=` and `argv[]=`.  Pull out
# absolute path tokens, then drop the first one (the executable itself).
# NixOS-generated configuration files are normally immutable regular files in
# /nix/store, but the same also works for /etc and /run paths.
mapfile -t initial_paths < <(
  printf '%s\n' "$exec_start" \
    | extract_paths \
    | awk '!seen[$0]++'
)

if (( ${#initial_paths[@]} > 0 )); then
  initial_paths=("${initial_paths[@]:1}")
fi

for path in "${initial_paths[@]}"; do
  consider_initial_path "$path"
done

paths=("${config_paths[@]}")
if (( ${#paths[@]} == 0 )); then
  paths=("${other_paths[@]}")
fi

if [[ $recursive == true ]]; then
  for path in "${paths[@]}"; do
    enqueue_recursive "$path" 0
  done

  recursive_index=0
  while (( recursive_index < ${#recursive_paths[@]} )); do
    path=${recursive_paths[recursive_index]}
    depth=${recursive_depths[recursive_index]}
    recursive_index=$((recursive_index + 1))
    (( depth < max_reference_depth )) || continue
    next_depth=$((depth + 1))

    mapfile -t referenced_paths < <(extract_paths < "$path")
    for referenced_path in "${referenced_paths[@]}"; do
      if [[ -f $referenced_path ]]; then
        add_candidate "$referenced_path" "$next_depth" "$path" || continue
        enqueue_recursive "$referenced_path" "$next_depth"
      elif [[ -d $referenced_path ]]; then
        expand_directory "$referenced_path" "$next_depth" "$path"
      fi
    done
  done

  paths=("${config_paths[@]}")
  if (( ${#paths[@]} == 0 )); then
    paths=("${other_paths[@]}")
  fi
fi

if (( ${#paths[@]} == 0 )); then
  echo "scf: no existing configuration path found in ExecStart of $service" >&2
  echo "ExecStart: $exec_start" >&2
  exit 1
fi

if [[ $interactive == false && -z $selection_number ]]; then
  selection_number=1
fi

if [[ -n $selection_number ]]; then
  if (( selection_number > ${#paths[@]} )); then
    echo "scf: candidate number $selection_number is out of range (1-${#paths[@]})" >&2
    exit 2
  fi
  config_path=${paths[selection_number - 1]}
elif (( ${#paths[@]} > 1 )); then
  declare -A displayed_indices=()
  for i in "${!paths[@]}"; do
    displayed_indices["${paths[i]}"]=$((i + 1))
  done

  printf '%sscf: multiple possible configuration files for %s:%s\n' \
    "$color_prompt" "$service" "$color_reset" >&2
  for i in "${!paths[@]}"; do
    path=${paths[i]}
    source=${candidate_source[$path]}
    if [[ $source == ExecStart ]]; then
      source_label=ExecStart
    elif [[ -n ${displayed_indices[$source]+seen} ]]; then
      source_label="#${displayed_indices[$source]}"
    else
      source_label=$source
    fi
    source_detail=${candidate_source_detail[$path]}
    if [[ -n $source_detail ]]; then
      source_label+=" ($source_detail)"
    fi
    printf '%s  %d) %s%s\n' \
      "$color_item" "$((i + 1))" "$path" "$color_reset" >&2
    printf '%s     depth: %d; derived from: %s%s\n' \
      "$color_detail" "${candidate_depth[$path]}" "$source_label" "$color_reset" >&2
  done
  read -r -p "${color_prompt}Select a file [1]: ${color_reset}" selection
  selection=${selection:-1}
  if [[ ! $selection =~ ^[0-9]+$ ]]; then
    echo "scf: invalid selection" >&2
    exit 2
  fi
  selection=$((10#$selection))
  if (( selection < 1 || selection > ${#paths[@]} )); then
    echo "scf: invalid selection" >&2
    exit 2
  fi
  config_path=${paths[selection - 1]}
else
  config_path=${paths[0]}
fi

if [[ $interactive == false ]]; then
  restore_locale
  exec cat -- "$config_path"
fi

editor=${VISUAL:-${EDITOR:-vim}}
read -r -a editor_command <<< "$editor"
if (( ${#editor_command[@]} == 0 )); then
  editor_command=(vim)
fi
restore_locale
exec "${editor_command[@]}" "$config_path"
