#!/usr/bin/env bash
# Local-only lint runner for the iOS coding standard.
#
# Nothing is written inside the repository. The config, the resolved config, the baseline and
# every report live under $WORK_ROOT, outside any git working tree. Adding SwiftLint to the
# project (a committed .swiftlint.yml, a build phase, a CI job) is a separate decision and this
# script deliberately does not take it.
#
#   ./lint-local.sh <module-path> [--write-baseline] [--strict] [--report json|xcode|emoji]
#
# First run for a module:
#   ./lint-local.sh Domains/<Module> --write-baseline     # grandfather what exists today
# Then:
#   ./lint-local.sh Domains/<Module> --strict             # only new violations surface

set -euo pipefail

SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Scratch root. `~/.claude` is one host's tree: defaulting there made a Copilot- or
# Codex-only machine create a stray .claude directory just to hold lint output. Prefer
# whichever host tree already exists, and fall back to a neutral location.
if [ -n "${IOS_STANDARD_WORK_ROOT:-}" ]; then
  WORK_ROOT="$IOS_STANDARD_WORK_ROOT"
else
  WORK_ROOT=""
  for _host_dir in "$HOME/.claude" "$HOME/.copilot" "$HOME/.codex"; do
    [ -d "$_host_dir" ] && { WORK_ROOT="$_host_dir/local/ios-coding-standard"; break; }
  done
  unset _host_dir
  [ -n "$WORK_ROOT" ] || WORK_ROOT="${TMPDIR:-/tmp}/ios-coding-standard"
fi
DRAFT_CONFIG="$SKILL_DIR/swiftlint.draft.yml"

MODULE_PATH="${1:-}"
shift || true

STRICT=""
WRITE_BASELINE=""
REPORTER="xcode"
while [ $# -gt 0 ]; do
  case "$1" in
    --strict) STRICT="--strict" ;;
    --write-baseline) WRITE_BASELINE="1" ;;
    --report) shift; REPORTER="${1:-xcode}" ;;
    *) echo "unknown option: $1" >&2; exit 2 ;;
  esac
  shift
done

if [ -z "$MODULE_PATH" ] || [ ! -d "$MODULE_PATH" ]; then
  echo "usage: $(basename "$0") <module-path> [--write-baseline] [--strict] [--report <kind>]" >&2
  exit 2
fi

if ! command -v swiftlint >/dev/null 2>&1; then
  cat >&2 <<'EOF'
swiftlint is not installed.

Install it for your user only  -  this does not touch the project:

    brew install swiftlint

If Homebrew is unavailable, a pinned local copy also works:

    mkdir -p ~/.local/bin && cd ~/.local/bin
    curl -sL https://github.com/realm/SwiftLint/releases/latest/download/portable_swiftlint.zip -o sl.zip
    unzip -o sl.zip && rm sl.zip && chmod +x swiftlint
    # then add ~/.local/bin to PATH

EOF
  exit 127
fi

REPO_ROOT="$(cd "$MODULE_PATH" && git rev-parse --show-toplevel)"
MODULE_ABS="$(cd "$MODULE_PATH" && pwd)"
MODULE_NAME="$(basename "$MODULE_ABS")"
MODULE_WORK="$WORK_ROOT/$MODULE_NAME"
mkdir -p "$MODULE_WORK"

case "$MODULE_WORK" in
  "$REPO_ROOT"/*) echo "refusing to write inside the repository: $MODULE_WORK" >&2; exit 1 ;;
esac

# ── Placeholder resolution ────────────────────────────────────────────────
# Per-module inventory, produced by the audit's Phase 2d. Optional: without it the
# sensitive-data rules fall back to a conservative generic pattern.
INVENTORY="$MODULE_WORK/inventory.env"
if [ -f "$INVENTORY" ]; then
  # shellcheck disable=SC1090
  . "$INVENTORY"
fi

# SENSITIVE / TRANSIENT: alternations of the module's own symbol names.
: "${SENSITIVE:=token|password|passcode|secret|credential|passport|nationalId|identityNumber|pnr|reservationCode|ticketNumber|membershipNumber|cardNumber|cvv|dateOfBirth}"
: "${TRANSIENT:=oneTimeCode|otp|verificationCode|draft}"

# SIBLINGS: every other module that lives beside this one  -  the MOD-01 import guard.
if [ -z "${SIBLINGS:-}" ]; then
  PARENT="$(dirname "$MODULE_ABS")"
  SIBLINGS="$(find "$PARENT" -mindepth 1 -maxdepth 1 -type d ! -name "$MODULE_NAME" -exec basename {} \; \
              | sort | paste -sd'|' -)"
fi
[ -n "$SIBLINGS" ] || SIBLINGS="__none__"

# LOGIC: the paths where TEST-01/03/05 and CONC-04 apply.
# Logic paths only. A Scene is NOT logic: `Task { await viewModel.action() }` in a button
# closure is the idiomatic sync->async bridge, and flagging it buries the real finding.
: "${LOGIC:=.*(ViewModel|UseCase|Repository|Mapper)[^/]*\\.swift}"

RESOLVED="$MODULE_WORK/swiftlint.resolved.yml"
# Substituted with python rather than sed: every replacement is a regex alternation full of
# delimiter characters, and sed has no delimiter that is safe against all of them.
SENSITIVE="$SENSITIVE" TRANSIENT="$TRANSIENT" SIBLINGS="$SIBLINGS" LOGIC="$LOGIC" \
python3 -c '
import os, sys
src, dst = sys.argv[1], sys.argv[2]
text = open(src).read()
for key in ("SENSITIVE", "TRANSIENT", "SIBLINGS"):
    # Wrapped in a non-capturing group: these values are alternations, and "|" binds looser than
    # concatenation. Substituting them bare turns "prefix\bA|B|C" into "(prefix\bA) or (B) or (C)",
    # which matches every line containing any bare term  -  the whole pattern silently collapses.
    text = text.replace("((%s))" % key, "(?:%s)" % os.environ[key])
text = text.replace("((LOGIC))", os.environ["LOGIC"])
open(dst, "w").write(text)
' "$DRAFT_CONFIG" "$RESOLVED"

BASELINE="$MODULE_WORK/baseline.json"

echo "module    : $MODULE_ABS"
echo "config    : $RESOLVED"
echo "workdir   : $MODULE_WORK   (outside the repo)"
echo "siblings  : $SIBLINGS"
[ -f "$INVENTORY" ] && echo "inventory : $INVENTORY" || echo "inventory : (none  -  using the generic fallback pattern)"
echo

cd "$MODULE_ABS"

if [ -n "$WRITE_BASELINE" ]; then
  echo "writing baseline -> $BASELINE"
  BL_PATHS=()
  for d in Sources Tests; do [ -d "$d" ] && BL_PATHS+=("$d"); done
  [ ${#BL_PATHS[@]} -gt 0 ] || BL_PATHS=(.)
  swiftlint lint --config "$RESOLVED" --write-baseline "$BASELINE" --quiet "${BL_PATHS[@]}" || true
  echo
  echo "baseline written. from now on run without --write-baseline; only new violations surface."
  exit 0
fi

LINT_PATHS=()
for d in Sources Tests; do [ -d "$d" ] && LINT_PATHS+=("$d"); done
[ ${#LINT_PATHS[@]} -gt 0 ] || LINT_PATHS=(.)

ARGS=(lint --config "$RESOLVED" --reporter "$REPORTER" --quiet)
[ -f "$BASELINE" ] && ARGS+=(--baseline "$BASELINE")
[ -n "$STRICT" ] && ARGS+=("$STRICT")

set +e
# stdout is the report, stderr is progress + config diagnostics  -  never merge them, or a
# machine-readable reporter comes back with progress lines spliced into it.
swiftlint "${ARGS[@]}" "${LINT_PATHS[@]}" 2>"$MODULE_WORK/last-run.err" | tee "$MODULE_WORK/last-run.txt"
STATUS=${PIPESTATUS[0]}
if [ -s "$MODULE_WORK/last-run.err" ]; then
  echo
  echo "swiftlint diagnostics -> $MODULE_WORK/last-run.err"
  grep -E '^(warning|error):' "$MODULE_WORK/last-run.err" | sort -u | head
fi
set -e

echo
echo "report: $MODULE_WORK/last-run.txt"
[ -f "$BASELINE" ] && echo "baseline in effect: $BASELINE  (delete it to see every existing violation)"
exit $STATUS
