#!/usr/bin/env bash
# Generator tolerates "no matches" exit codes from grep | sort.
# pipefail and -e off; -u stays.
set -u

# nomarchy-docs-scripts
#
# Regenerates docs/SCRIPTS.md from the repo state. Produces:
#   1. Header + status legend + regen instructions.
#   2. Table of every nomarchy-* script (location, callers, status).
#   3. Table of every menu entry in features/scripts/utils/nomarchy-menu
#      (submenu, label, target command, status).
#   4. Snapshot list of orphaned references (called somewhere, no script).
#
# Status heuristic in Phase A:
#   kept     — file exists AND is called from at least one *.nix / *.conf /
#              shell file outside its own directory.
#   unused?  — file exists but no caller found. Phase B decides delete-dead
#              vs intentional public API.
#   missing  — referenced but no file. Phase B decides port-from-omarchy
#              vs delete-dead vs stub-with-notify.
#
#   nomarchy-docs-scripts                       # write to stdout
#   nomarchy-docs-scripts --out docs/SCRIPTS.md

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

# --- Inventory -------------------------------------------------------------

# Where scripts live, in render order.
declare -A loc_label=(
  ["features/scripts/utils"]="features/scripts/utils"
  ["core/system/scripts"]="core/system/scripts"
  ["themes/engine/scripts"]="themes/engine/scripts"
)
script_dirs=(features/scripts/utils core/system/scripts themes/engine/scripts)

# Build name → location map (associative array of basename → repo-relative dir).
declare -A script_loc
for dir in "${script_dirs[@]}"; do
  [[ -d "$dir" ]] || continue
  while IFS= read -r f; do
    script_loc["$(basename "$f")"]="$dir"
  done < <(find "$dir" -maxdepth 1 -type f -name 'nomarchy-*')
done

# Find every nomarchy-* token referenced anywhere outside the script dirs.
# (We exclude the script files themselves so they don't list themselves as
# their own caller.)
# File types we search for references. *.md catches docs and README;
# branding/hook/extension files have varied or no extensions.
# *.lua catches elephant providers; *.ini catches mako on-button-* hooks;
# *.desktop catches MimeType-registered URL handlers.
grep_includes=(
  --include='*.nix' --include='*.conf' --include='*.sh' --include='*.md'
  --include='nomarchy-*' --include='*.jsonc' --include='*.json'
  --include='*.toml' --include='*.ini' --include='*.lua'
  --include='*.desktop' --include='*.txt' --include='*.sample'
)
search_dirs=(core features themes installer hosts bin lib README.md)

# Files whose mentions of nomarchy-* are documentation about the scripts,
# not real callers. Excluded from caller discovery so they don't promote
# every script to `kept`.
self_refs=(docs/SCRIPTS.md docs/ROADMAP.md docs/AGENT.md)

ref_files_per_cmd() {
  local cmd="$1"
  local self_pattern
  self_pattern=$(IFS='|'; echo "${self_refs[*]}")
  grep -rlE "\\b${cmd}\\b" \
    "${grep_includes[@]}" \
    "${search_dirs[@]}" 2>/dev/null \
    | grep -vE "^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/${cmd}$" \
    | grep -vE "^(${self_pattern})$" \
    | sort -u
}

# All distinct nomarchy-* tokens we see anywhere in the repo.
# Final char must be alphanumeric — dropping trailing-dash matches like
# `nomarchy-pkg-` that come from glob references (`for c in nomarchy-pkg-*`).
# Restrict to grep_includes so binaries / tmpfiles don't pollute the set.
all_refs=$(grep -rohE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \
            "${grep_includes[@]}" \
            "${search_dirs[@]}" 2>/dev/null \
          | sort -u)

# --- Render: header --------------------------------------------------------

main() {
  cat <<'HEADER'
# Nomarchy Script & Menu Audit

Auto-generated table for [Pillar 3 of the roadmap](ROADMAP.md#3-pillar-script--menu-audit).
**Do not edit by hand.** Regenerate after script or menu changes:

```bash
./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md
```

The status column uses a Phase A heuristic — `kept` / `unused?` / `missing`.
Phase B (per-batch PRs) refines those into `port-from-omarchy`,
`delete-dead`, or `stub-with-notify` and updates the rows.

## Status legend

- `kept` — script exists and is called from somewhere outside its own directory.
- `unused?` — script exists but no caller was found. Could be dead, could be
  intentional public API. Phase B triage decides.
- `missing` — referenced from code but no script file exists. Phase B triage
  decides whether to port from Omarchy upstream, delete the caller, or stub
  with `notify-send`.
- `port-from-omarchy` — Phase B verdict: lift the upstream Omarchy script,
  rewrite for NixOS paths.
- `delete-dead` — Phase B verdict: remove and update callers.
- `stub-with-notify` — Phase B verdict: temporary `notify-send` stub.

HEADER

  # --- Render: scripts table ----------------------------------------------
  printf '## Scripts (%d)\n\n' "${#script_loc[@]}"
  printf '| Script | Location | Callers | Status | Notes |\n'
  printf '| --- | --- | --- | --- | --- |\n'

  # Sort scripts by name.
  for name in $(printf '%s\n' "${!script_loc[@]}" | sort); do
    local dir="${script_loc[$name]}"
    local callers status callers_str
    callers=$(ref_files_per_cmd "$name")
    if [[ -z "$callers" ]]; then
      status='`unused?`'
      callers_str='—'
    else
      status='`kept`'
      # Trim caller list to 2 entries + count.
      local count
      count=$(printf '%s\n' "$callers" | wc -l)
      if (( count > 2 )); then
        callers_str=$(printf '%s\n' "$callers" | head -2 | paste -sd, -)
        callers_str="$callers_str, +$((count - 2)) more"
      else
        callers_str=$(printf '%s\n' "$callers" | paste -sd, -)
      fi
    fi
    printf '| `%s` | `%s` | %s | %s |  |\n' \
      "$name" "$dir" "$callers_str" "$status"
  done
  echo

  # --- Render: missing references -----------------------------------------
  printf '## Missing references\n\n'
  printf 'Tokens grepped from `core/`, `features/`, `themes/`, `installer/`, `hosts/`, `bin/`, `lib/` that have no matching script file.\n\n'
  printf '| Token | Referenced in | Status |\n'
  printf '| --- | --- | --- |\n'
  while IFS= read -r token; do
    [[ -z "$token" ]] && continue
    [[ -n "${script_loc[$token]:-}" ]] && continue
    local refs
    self_pattern=$(IFS='|'; echo "${self_refs[*]}")
    refs=$(grep -rlE "\\b${token}\\b" \
            "${grep_includes[@]}" \
            "${search_dirs[@]}" 2>/dev/null \
          | grep -vE "^(${self_pattern})$" \
          | sort -u)
    [[ -z "$refs" ]] && continue
    local count refs_str
    count=$(printf '%s\n' "$refs" | wc -l)
    if (( count > 2 )); then
      refs_str=$(printf '%s\n' "$refs" | head -2 | paste -sd, -)
      refs_str="$refs_str, +$((count - 2)) more"
    else
      refs_str=$(printf '%s\n' "$refs" | paste -sd, -)
    fi
    printf '| `%s` | %s | `missing` |\n' "$token" "$refs_str"
  done <<<"$all_refs"
  echo

  # --- Render: menu items -------------------------------------------------
  printf '## Menu items\n\n'
  printf 'Walked from `features/scripts/utils/nomarchy-menu`. Each `case` arm in a `show_*_menu` function becomes one row.\n\n'
  printf '| Submenu | Entry | Calls | Status |\n'
  printf '| --- | --- | --- | --- |\n'

  awk '
    /^show_[a-z_]+_menu\(\) {/ { sub(/\(\) {/, ""); current=$1; in_func=1; next }
    /^[a-z_]+\(\) {/ && !/^show_/ { current=""; in_func=0; next }
    /^}$/ { current=""; in_func=0; next }
    !in_func { next }
    /^  case \$\(menu / {
      # extract the menu title between the first pair of double quotes
      match($0, /menu "[^"]+" "[^"]+"/);
      if (RSTART == 0) next;
      title=substr($0, RSTART, RLENGTH);
      # second quoted string is the option list
      n=split(title, parts, "\"");
      title=parts[2];
      options=parts[4];
      # split options on \n
      split(options, opts, "\\\\n");
      pending_submenu=current;
      pending_title=title;
      for (i=1;i<=length(opts);i++) pending_opts[i]=opts[i];
      pending_count=length(opts);
      next
    }
    /^  \*[A-Za-z]/ {
      # case arm — extract pattern between the first * and the closing )
      match($0, /\*[^)]*\)/);
      if (RSTART == 0) next;
      arm=substr($0, RSTART, RLENGTH);
      gsub(/[*)]/, "", arm);
      gsub(/^[[:space:]]+|[[:space:]]+$/, "", arm);
      # action follows the )
      rest=substr($0, RSTART+RLENGTH);
      sub(/^[[:space:]]+/, "", rest);
      sub(/[[:space:]]*;;[[:space:]]*$/, "", rest);
      # match the first nomarchy-* token in the action
      cmd=""
      if (match(rest, /nomarchy-[a-z0-9-]+/)) {
        cmd=substr(rest, RSTART, RLENGTH);
      }
      printf "%s|%s|%s\n", pending_submenu, arm, cmd;
    }
  ' features/scripts/utils/nomarchy-menu > /tmp/nomarchy-menu-rows.$$

  while IFS='|' read -r submenu entry cmd; do
    [[ -z "$entry" ]] && continue
    [[ "$entry" =~ ^\) ]] && continue
    status='`kept`'
    if [[ -n "$cmd" ]]; then
      if [[ -z "${script_loc[$cmd]:-}" ]]; then
        status='`missing`'
      fi
    else
      cmd='_(inline)_'
    fi
    printf '| `%s` | %s | `%s` | %s |\n' "$submenu" "$entry" "$cmd" "$status"
  done < /tmp/nomarchy-menu-rows.$$
  rm -f /tmp/nomarchy-menu-rows.$$
  echo
}

out=""
if [[ "${1:-}" == "--out" ]]; then
  out="${2:?--out needs a path}"; shift 2
fi
if [[ -n "$out" ]]; then
  main >"$out"
else
  main
fi
