#!/usr/bin/env bash
# scripts/preflight.sh — local reproduction of the CI gates before `git push`.
# Generated by ~/claude-config/scripts/gen-preflight.py from the workflows below;
# every command is the one CI runs. A step this file does not cover is a step
# that can only fail remotely — when a workflow step is added, regenerate or
# add it here in the same commit. ~/.claude/hooks/pre-push-preflight.sh runs
# `--quick` before every push and blocks on failure.
#
# usage: scripts/preflight.sh [--quick]   (--quick skips the test / bench suites)
set -euo pipefail
cd "$(dirname "$0")/.."

quick=0
[[ "${1:-}" == "--quick" ]] && quick=1

step() { printf '\n\033[1;34m== %s\033[0m\n' "$*"; }
# `cargo clippy` reuses fresh `cargo check` artifacts and then lints nothing;
# touching the crate roots invalidates only this repo's fingerprints.
relint() { git ls-files | grep -E '(^|/)src/(lib|main)\.rs$' | xargs -r touch; }
need() { command -v "$1" >/dev/null 2>&1 || { echo "missing tool: $1 ($2)" >&2; exit 1; }; }
has_toolchain() { rustup toolchain list | grep -q "^$1"; }

# Steps CI runs that this file cannot reproduce locally (they can only fail remotely):
#   - security-audit.yml:audit:Install cargo-audit (needs network / runner-only)
#   - security-audit.yml:deny:Install cargo-deny (needs network / runner-only)
#   - security-audit.yml:coverage (job is continue-on-error: informational in CI)
#   - security-audit.yml:semver-checks (job is continue-on-error: informational in CI)
#   - fuzz.yml:fuzz:Install cargo-fuzz (needs network / runner-only)
#   - fuzz.yml:fuzz:Build fuzz target (needs network / runner-only)
#   - fuzz.yml:fuzz:Set fuzz duration (no cargo / grep)
#   - fuzz.yml:fuzz:Run fuzz target (time-boxed) (continue-on-error)
#   - fuzz.yml:fuzz:Report crash (informational) (no cargo / grep)

need actionlint "brew install actionlint"
need cargo-audit "cargo install cargo-audit --locked"
need cargo-deny "cargo install cargo-deny --locked"
need cargo-hack "cargo install cargo-hack --locked"
need cargo-machete "cargo install cargo-machete --locked"
has_toolchain 1.88 || { echo "missing toolchain 1.88 (rustup toolchain install 1.88)" >&2; exit 1; }

step "ci.yml / clippy: Clippy (default features)"
relint
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo clippy --all-targets -- -W clippy::pedantic -D warnings )

step "ci.yml / clippy: Clippy (full native feature set)"
relint
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo clippy --features "$NATIVE_FEATURES" --all-targets -- -W clippy::pedantic -D warnings )

step "ci.yml / no_std: Check (no_std, host, no alloc)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo check --lib --no-default-features )

step "ci.yml / no_std: Check (no_std + alloc, host)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo check --lib --no-default-features --features alloc )

step "ci.yml / no_std: Check (no_std + alloc + custom-rng, bare-metal thumbv7em-none-eabihf)"
rustup target list --installed | grep -q '^thumbv7em-none-eabihf$' || rustup target add thumbv7em-none-eabihf
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo check --lib --no-default-features --features alloc,custom-rng --target thumbv7em-none-eabihf )

step "ci.yml / no_std: Clippy (no_std + alloc + custom-rng, bare-metal)"
relint
rustup target list --installed | grep -q '^thumbv7em-none-eabihf$' || rustup target add thumbv7em-none-eabihf
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo clippy --lib --no-default-features --features alloc,custom-rng --target thumbv7em-none-eabihf -- -D warnings )

step "ci.yml / msrv: Check (default + full native feature set)"
(
  export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"
  cargo +1.88 check --lib
  cargo +1.88 check --lib --features "$NATIVE_FEATURES"
)

step "ci.yml / feature-powerset: Powerset (std + {alloc, serde, ffi, db, api, custom-rng} depth 2)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo hack check --lib --feature-powerset --depth 2 --exclude-features pyo3,crypto,nizk --features std )

step "ci.yml / fmt: Check formatting"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo fmt -- --check )

step "ci.yml / doc: Doc (full native feature set)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api" RUSTDOCFLAGS="-Dwarnings"; cargo doc --no-deps --features "$NATIVE_FEATURES" )

step "ci.yml / actionlint: actionlint"
actionlint .github/workflows/*.yml

step "security-audit.yml / deny: Run cargo deny check all"
( export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"; cargo deny --all-features check all )

step "security-audit.yml / unused-deps: cargo machete"
cargo machete

step "security-audit.yml / stub-guard: Block panic!(STUB) in src/**"
(
  export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"
  set -eo pipefail
  hits=$(grep -rnE 'panic!\([^)]*STUB' src/ --include="*.rs" --exclude-dir=bin \
    | grep -v ':[[:space:]]*//' | grep -vE ':[[:space:]]*/\*' || true)
  if [ -n "$hits" ]; then echo "❌"; echo "$hits"; exit 1; fi
  echo "✓ No panic!(STUB) in src/"
)

step "security-audit.yml / stub-guard: Detect todo! / unimplemented! (informational)"
(
  export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"
  set -eo pipefail
  hits=$(grep -rnE 'todo!\(|unimplemented!\(' src/ --include="*.rs" --exclude-dir=bin \
    | grep -v ':[[:space:]]*//' | grep -vE ':[[:space:]]*/\*' || true)
  if [ -n "$hits" ]; then
    count=$(echo "$hits" | wc -l | tr -d ' ')
    echo "::warning::${count} todo!()/unimplemented!() marker(s) (informational, CLAUDE.md legitimate fail-fast idiom)"
  fi
)

step "security-audit.yml / stub-guard: Detect dbg!() residual in src/**"
(
  export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"
  set -eo pipefail
  hits=$(grep -rn 'dbg!(' src/ --include="*.rs" || true)
  if [ -n "$hits" ]; then echo "❌"; echo "$hits"; exit 1; fi
  echo "✓ No dbg!() in src/"
)

step "security-audit.yml / stub-guard: Detect TODO / FIXME / XXX / HACK (informational)"
(
  export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"
  set -eo pipefail
  hits=$(grep -rnE 'TODO|FIXME|XXX|HACK' src/ --include="*.rs" || true)
  if [ -n "$hits" ]; then
    echo "::warning::TODO/FIXME/XXX/HACK found in src/ (informational, not blocking):"
    echo "$hits" | head -50
  else
    echo "✓ No TODO/FIXME/XXX/HACK in src/"
  fi
)

step "fuzz.yml / build every fuzz target (nightly; the replay needs the runner)"
if has_toolchain nightly && cargo +nightly fuzz --version >/dev/null 2>&1; then
  (cd fuzz && cargo +nightly fuzz build)
else
  echo "skip: nightly / cargo-fuzz not installed" >&2
fi

if [[ $quick -eq 1 ]]; then
  echo; echo "preflight --quick OK (test / bench suites skipped)"; exit 0
fi

step "ci.yml / test: Test (default)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo test )

step "ci.yml / test: Test (full native feature set)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo test --features "$NATIVE_FEATURES" )

step "ci.yml / test: Test (release、full native feature set)"
( export CARGO_TERM_COLOR="always" RUSTFLAGS="-Dwarnings" NATIVE_FEATURES="std,serde,ffi,db,api"; cargo test --release --features "$NATIVE_FEATURES" )

step "security-audit.yml / audit: Run cargo audit"
(
  export CARGO_TERM_COLOR="always" CARGO_NET_RETRY="5" CARGO_HTTP_MULTIPLEXING="false"
  cargo audit \
    --deny yanked \
    --ignore RUSTSEC-2024-0436 \
    --ignore RUSTSEC-2025-0020 \
    --ignore RUSTSEC-2025-0141 \
    --ignore RUSTSEC-2026-0176 \
    --ignore RUSTSEC-2026-0177 \
    --ignore RUSTSEC-2026-0192
)

echo; echo "preflight OK"
