#!/bin/sh
# keel-verify — the project's own release linter (Keel Phase 5 §1a).
#
# Mechanical only: it verifies that what the docs PROMISE is actually true, so no session
# re-checks it by eye and no release ships with a promise broken. Runs at every sprint close
# and at the Phase 7 gate; its output is pasted as evidence.
#
# Dependency-light on purpose: POSIX sh + grep/awk/sed, nothing the toolchain lacks.
# Exit 0 = all checks passed (skips are not failures). Exit 1 = at least one FAIL.
#
# Usage: scripts/keel-verify

cd "$(dirname "$0")/.." || exit 1

fail=0
pass_count=0
fail_count=0
skip_count=0

ok()   { printf '  PASS  %s\n' "$1"; pass_count=$((pass_count + 1)); }
bad()  { printf '  FAIL  %s\n' "$1"; fail_count=$((fail_count + 1)); fail=1; }
skip() { printf '  SKIP  %s\n' "$1"; skip_count=$((skip_count + 1)); }

printf '\nkeel-verify — WooCommerce Servired/RedSys Spain Gateway\n'
printf '=======================================================\n'

# ---------------------------------------------------------------------------
# 1) Version touchpoints in sync (docs/03-technical-plan.md §8)
# ---------------------------------------------------------------------------
printf '\n[1] Version touchpoints\n'

v_header=$(grep -m1 '^ \* Version:' woocommerce-gateway-redsys.php | sed 's/.*Version:[[:space:]]*//' | tr -d '\r')
v_define=$(grep -m1 "define( 'REDSYS_VERSION'" woocommerce-gateway-redsys.php | sed "s/.*'REDSYS_VERSION',[[:space:]]*'\([^']*\)'.*/\1/")
v_readme=$(grep -m1 '^Stable tag:' readme.txt | sed 's/^Stable tag:[[:space:]]*//' | tr -d '\r')
v_change=$(grep -m1 -E '^[0-9]{4}\.[0-9]{2}\.[0-9]{2} - version' changelog.txt | sed 's/.*version[[:space:]]*//' | tr -d '\r')

printf '        main header=%s  define=%s  readme=%s  changelog=%s\n' \
  "$v_header" "$v_define" "$v_readme" "$v_change"

if [ -n "$v_header" ] && [ "$v_header" = "$v_define" ] && [ "$v_header" = "$v_readme" ] && [ "$v_header" = "$v_change" ]; then
  ok "the 4 canonical version touchpoints agree ($v_header)"
else
  bad "version touchpoints disagree — see docs/03-technical-plan.md §8"
fi

# package.json and the .pot are recorded as knowingly stale (audit F-items). Report them,
# but do not fail the run on a gap the project has already accepted on the record.
v_pkg=$(grep -m1 '"version"' package.json | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
v_pot=$(grep -m1 'Project-Id-Version' languages/woocommerce-redsys.pot 2>/dev/null | sed 's/.*Project-Id-Version:[[:space:]]*//' | sed 's/\\n.*//' | tr -d '"\r')
[ "$v_pkg" = "$v_header" ] && ok "package.json version matches" \
  || skip "package.json is '$v_pkg' vs '$v_header' — known, recorded in docs/03-technical-plan.md §8 (nothing reads it)"
case "$v_pot" in
  *"$v_header"*) ok ".pot Project-Id-Version matches" ;;
  *) skip ".pot Project-Id-Version is '$v_pot' vs '$v_header' — known (audit D-5, folded into the next Phase 7)" ;;
esac

# ---------------------------------------------------------------------------
# 2) Changelog order
# ---------------------------------------------------------------------------
printf '\n[2] Changelog order\n'
# NOTE: this project records NEWEST-FIRST (docs/03-technical-plan.md:165), deliberately the
# opposite of Keel's generic oldest-first wording. The project's recorded convention governs.
dates=$(grep -E '^[0-9]{4}\.[0-9]{2}\.[0-9]{2} - version' changelog.txt | sed 's/ - version.*//' | tr -d '\r')
if [ -z "$dates" ]; then
  bad "no dated entries found in changelog.txt"
else
  sorted=$(printf '%s\n' "$dates" | sort -r)
  if [ "$dates" = "$sorted" ]; then
    ok "changelog.txt is newest-first ($(printf '%s\n' "$dates" | wc -l | tr -d ' ') entries)"
  else
    bad "changelog.txt is not in newest-first order"
  fi
fi

# ---------------------------------------------------------------------------
# 3) docs/api/INDEX.md parity (D-007: a '—' Doc cell is legitimate; docs are backfilled)
# ---------------------------------------------------------------------------
printf '\n[3] docs/api INDEX parity\n'

missing_doc=0
for ref in $(grep -oE 'docs/(api|reference)/[A-Za-z0-9_.-]+\.md' docs/api/INDEX.md | sort -u); do
  [ -f "$ref" ] || { bad "INDEX references a doc that does not exist: $ref"; missing_doc=1; }
done
[ "$missing_doc" -eq 0 ] && ok "every doc referenced by INDEX.md exists"

orphan=0
for doc in docs/api/*.md docs/reference/*.md; do
  [ -f "$doc" ] || continue
  # Skip INDEX.md itself and the canonical Phase 6 STRUCTURAL docs — the api index/
  # conventions README and the consolidated class/function/hook references
  # (references/phase-6-documentation.md). These are not per-surface entries, so they
  # carry no INDEX.md row by design (D-044). Per-surface docs still must (D-007).
  case "$doc" in
    */INDEX.md|docs/api/README.md|docs/reference/classes.md|docs/reference/functions.md|docs/reference/hooks-and-extension-points.md)
      continue ;;
  esac
  base=$(basename "$doc")
  grep -q "$base" docs/api/INDEX.md || { bad "doc has no row in INDEX.md: $doc"; orphan=1; }
done
[ "$orphan" -eq 0 ] && ok "every doc in docs/api and docs/reference has an INDEX row"

# ---------------------------------------------------------------------------
# 4) No placeholder copy in distributable surfaces
# ---------------------------------------------------------------------------
printf '\n[4] Placeholder copy in shipped surfaces\n'
ph=0
for f in readme.txt changelog.txt; do
  [ -f "$f" ] || continue
  if grep -nE '(TODO|FIXME|lorem ipsum|LOREM IPSUM|XXX-PLACEHOLDER)' "$f" >/dev/null 2>&1; then
    bad "$f contains placeholder/TODO copy"; ph=1
  fi
done
[ "$ph" -eq 0 ] && ok "readme.txt and changelog.txt carry no placeholder copy"

# ---------------------------------------------------------------------------
# 5) Unresolved evidence placeholders in the living state
#     Project-specific: slice evidence cells must carry a REAL commit hash. A `<1.3>`-style
#     placeholder left behind means the evidence is unverifiable — this has happened twice.
# ---------------------------------------------------------------------------
printf '\n[5] Evidence placeholders in docs\n'
if grep -rnE '`<[0-9]+\.[0-9]+>`' docs/05-test-points.md docs/sprints/ >/dev/null 2>&1; then
  bad "unresolved commit-hash placeholder in a test-point or sprint evidence cell"
  grep -rnE '`<[0-9]+\.[0-9]+>`' docs/05-test-points.md docs/sprints/ | sed 's/^/        /'
else
  ok "no unresolved commit-hash placeholders"
fi

# Every recorded short hash must actually resolve (an amended commit invalidates it).
badhash=0
for h in $(grep -rhoE 'Commit `[0-9a-f]{7,40}`' docs/05-test-points.md | sed 's/Commit `//;s/`//' | sort -u); do
  git cat-file -t "$h" >/dev/null 2>&1 || { bad "recorded commit hash does not resolve: $h"; badhash=1; }
done
[ "$badhash" -eq 0 ] && ok "every recorded commit hash resolves"

# ---------------------------------------------------------------------------
# 6) Direct-access guards (the promise slice 1.4 made)
# ---------------------------------------------------------------------------
printf '\n[6] Direct-access guards on shipped PHP\n'
unguarded=0
for f in $(git ls-files '*.php' | grep -vE '^(desarrollo/|vendor/|node_modules/|tests/)' | grep -vE '/(vendor|node_modules)/' | grep -vE '(\.asset\.php|^languages/.*\.l10n\.php)$'); do
  base=$(basename "$f")
  [ "$base" = "index.php" ] && continue          # "Silence is golden" files
  grep -q "defined( *['\"]ABSPATH['\"] *)" "$f" || { printf '        %s\n' "$f"; unguarded=$((unguarded + 1)); }
done
if [ "$unguarded" -eq 0 ]; then
  ok "every shipped first-party PHP file has an ABSPATH guard"
else
  bad "$unguarded shipped PHP file(s) lack an ABSPATH guard"
fi

# ---------------------------------------------------------------------------
# 7) Blocks bundle in sync with its source
#
# assets/js/frontend/blocks.js is BUILT from resources/js/frontend/index.js by wp-scripts.
# Unlike the hand-maintained assets/js/*.min.js (audit C-1), this one has a real pipeline, so
# drift between source and artifact is mechanically detectable — and drift here is expensive:
# the artifact is what runs in the shop, so a stale bundle means reading the source tells you
# nothing about the site's behaviour. Rebuild into a temporary output and compare.
# ---------------------------------------------------------------------------
printf '\n[7] Blocks bundle in sync with its source\n'
blocks_artifact='assets/js/frontend/blocks.js'
if [ ! -f "$blocks_artifact" ]; then
  bad "$blocks_artifact is missing — the Blocks integration cannot load"
elif [ ! -x node_modules/.bin/wp-scripts ]; then
  skip "Blocks bundle sync needs 'npm install' (wp-scripts absent)"
else
  # Build into a THROWAWAY directory, never over the tracked artifact. A verifier that writes
  # to the working tree can race with anything else reading those files (it made a test run
  # fail exactly once before this was fixed) and would leave the tree dirty on a mismatch.
  blocks_tmp=$(mktemp -d 2>/dev/null || printf '/tmp/keel-blocks-%s' "$$")
  if NODE_OPTIONS=--openssl-legacy-provider node_modules/.bin/wp-scripts build \
       --output-path="$blocks_tmp" >/dev/null 2>&1 && [ -f "$blocks_tmp/blocks.js" ]; then
    if cmp -s "$blocks_artifact" "$blocks_tmp/blocks.js"; then
      ok "assets/js/frontend/blocks.js matches a fresh build of its source"
    else
      bad "assets/js/frontend/blocks.js is STALE — rebuild with 'npm run build' and commit it"
    fi
  else
    skip "Blocks bundle sync: the build did not run (check 'npm run build')"
  fi
  rm -rf "$blocks_tmp"
fi

# ---------------------------------------------------------------------------
# 8) Checks that cannot run yet — reported, never silently passed
# ---------------------------------------------------------------------------
printf '\n[8] Deferred checks\n'
if command -v wp >/dev/null 2>&1; then
  ok "wp-cli present — wire the 'wp i18n make-pot' dry run here"
else
  skip "i18n check needs wp-cli or PHPCS i18n sniffs (audit F-8, Sprint 2)"
fi
skip "minified-asset sync needs the build/minify script (audit C-1, Sprint 2)"

# ---------------------------------------------------------------------------
printf '\n=======================================================\n'
printf 'PASS %s   FAIL %s   SKIP %s\n\n' "$pass_count" "$fail_count" "$skip_count"
exit "$fail"
