#!/usr/bin/env bash
set -euo pipefail

REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
SOURCE="${REPO_ROOT}/sql/pgcontext--0.3.0.sql"
OPTIONS_SOURCE="${REPO_ROOT}/crates/context-pg/src/hnsw_am/options.rs"
IVFFLAT_OPTIONS_SOURCE="${REPO_ROOT}/crates/context-pg/src/ivfflat_am/options.rs"
SETTINGS_SOURCE="${REPO_ROOT}/crates/context-pg/src/settings.rs"
POLICY_SOURCE="${REPO_ROOT}/crates/context-core/src/policy.rs"
CATALOG_SOURCE="${REPO_ROOT}/crates/context-pg/src/contract/contract_catalog_objects.rs"
OUTPUT="${REPO_ROOT}/docs/user_guide/sql_object_inventory.md"
CHECK_ONLY=false

if [[ "${1:-}" == "--check" ]]; then
  CHECK_ONLY=true
elif [[ $# -ne 0 ]]; then
  echo "usage: $0 [--check]" >&2
  exit 2
fi

for path in "${SOURCE}" "${OPTIONS_SOURCE}" "${IVFFLAT_OPTIONS_SOURCE}" "${SETTINGS_SOURCE}" \
  "${POLICY_SOURCE}" "${CATALOG_SOURCE}"; do
  if [[ ! -f "${path}" ]]; then
    echo "missing inventory source: ${path}" >&2
    exit 1
  fi
done

tmp="$(mktemp "${TMPDIR:-/tmp}/pgcontext-sql-inventory.XXXXXX")"
source_reloptions="$(mktemp "${TMPDIR:-/tmp}/pgcontext-source-reloptions.XXXXXX")"
documented_reloptions="$(mktemp "${TMPDIR:-/tmp}/pgcontext-documented-reloptions.XXXXXX")"
source_quantization="$(mktemp "${TMPDIR:-/tmp}/pgcontext-source-quantization.XXXXXX")"
documented_quantization="$(mktemp "${TMPDIR:-/tmp}/pgcontext-documented-quantization.XXXXXX")"
trap 'rm -f "${tmp}" "${source_reloptions}" "${documented_reloptions}" "${source_quantization}" "${documented_quantization}"' EXIT

count() {
  grep -Ec "$1" "${SOURCE}" || true
}

unique_type_count() {
  awk '/^CREATE TYPE / { name = $3; gsub(/[;(]/, "", name); seen[name] = 1 }
       END { for (name in seen) count++; print count + 0 }' "${SOURCE}"
}

operator_count() {
  awk '/^CREATE OPERATOR / && $3 != "CLASS" { count++ }
       END { print count + 0 }' "${SOURCE}"
}

{
  printf '%s\n\n' '# Installed SQL Object And Option Inventory'
  printf '%s\n' 'Generated by `scripts/generate-sql-object-inventory.sh` from'
  printf '%s\n' '`sql/pgcontext--0.3.0.sql` and the HNSW reloptions callback. Run the generator'
  printf '%s\n' 'after changing the installed SQL contract, or use'
  printf '%s\n' '`scripts/generate-sql-object-inventory.sh --check` to verify it. Do not edit'
  printf '%s\n\n' 'this file by hand.'
  printf '%s\n\n' 'The SQL contract registry owns lifecycle classification; this inventory pins the installed object and option shape consumed by the capability contract.'
  printf 'Full SQL artifact SHA-256: `%s`\n\n' "$(shasum -a 256 "${SOURCE}" | awk '{ print $1 }')"
  printf '%s\n\n' 'The artifact fingerprint covers every object declaration, function result shape, cast method/context, operator identity, and opclass strategy. `contract_registry` separately compares installed functions and catalog objects bidirectionally, including typed operator and access-method/input-type opclass identities.'
  printf '%s\n' '| Object kind | Installed count |'
  printf '%s\n' '|---|---:|'
  printf '| Types | %s |\n' "$(unique_type_count)"
  printf '| Schemas | %s |\n' "$(count '^CREATE SCHEMA ')"
  printf '| Functions | %s |\n' "$(count '^CREATE  FUNCTION |^CREATE FUNCTION ')"
  printf '| Tables | %s |\n' "$(count '^CREATE TABLE ')"
  printf '| Views | %s |\n' "$(count '^CREATE VIEW ')"
  printf '| Triggers | %s |\n' "$(count '^CREATE TRIGGER ')"
  printf '| Casts | %s |\n' "$(count '^CREATE CAST ')"
  printf '| Operators | %s |\n' "$(operator_count)"
  printf '| Operator classes | %s |\n' "$(count '^CREATE OPERATOR CLASS ')"
  printf '| Aggregates | %s |\n' "$(count '^CREATE AGGREGATE ')"
  printf '| Access methods | %s |\n\n' "$(count '^CREATE ACCESS METHOD ')"

  printf '%s\n\n' '## Types'
  awk '/^CREATE TYPE / { name = $3; gsub(/[;(]/, "", name); print "- `" name "`" }' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## Schemas, Tables, Views, And Triggers'
  awk '
    /^CREATE SCHEMA / { print "- schema `pgcontext`" }
    /^CREATE TABLE / { name = $3; gsub(/[;(]/, "", name); print "- table `" name "`" }
    /^CREATE VIEW / { name = $3; gsub(/[;(]/, "", name); print "- view `" name "`" }
    /^CREATE TRIGGER / { name = $3; gsub(/[;(]/, "", name); print "- trigger `" name "`" }
  ' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## Function Names'
  printf '%s\n\n' 'Overload argument and result identities are pinned by the artifact fingerprint and bidirectional `contract_registry` test.'
  awk '/^CREATE  FUNCTION / || /^CREATE FUNCTION / {
         name = $3; sub(/\(.*/, "", name); print "- `" name "`"
       }' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## Cast Identities'
  awk '/^CREATE CAST / { text = $0; sub(/^CREATE CAST /, "", text); print "- `" text "`" }' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## Typed Operator Registry Entries'
  printf '%s\n' '| Entry | Kind | Applies to | Lifecycle |'
  printf '%s\n' '|---|---|---|---|'
  awk '/pgcontext_operator\(/ || /pgcontext_operator_class\(/ {
         kind = ($0 ~ /pgcontext_operator_class\(/) ? "operator class" : "operator"
         split($0, q, "\"")
         life = "—"
         if ($0 ~ /SqlLifecycle::Stable/) life = "Stable"
         else if ($0 ~ /SqlLifecycle::Experimental/) life = "Experimental"
         name = q[2]; applies = q[4]
         gsub(/\|/, "\\|", name); gsub(/\|/, "\\|", applies)
         printf "| `%s` | %s | %s | %s |\n", name, kind, applies, life
       }' "${CATALOG_SOURCE}"

  printf '\n%s\n\n' '## Aggregate Identities'
  awk '/^CREATE AGGREGATE / { text = $0; sub(/^CREATE AGGREGATE /, "", text); print "- `" text "`" }' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## Access Methods And Operator Classes'
  awk '
    /^CREATE ACCESS METHOD / { name = $4; gsub(/[;(]/, "", name); print "- access method `" name "`" }
    /^CREATE OPERATOR CLASS / { name = $4; gsub(/[;(]/, "", name); print "- operator class `" name "`" }
  ' "${SOURCE}" | sort -u

  printf '\n%s\n\n' '## `pgcontext_hnsw` Index Reloptions'
  printf '%s\n' '| Option | Accepted shape | Lifecycle |'
  printf '%s\n' '|---|---|---|'
  printf '%s\n' '| `quantization` | `none`, `scalar`, `sq8`, `pq`, or `binary` | Candidate codec; exact source rerank is mandatory |'
  printf '%s\n' '| `scalar_min` | finite lower bound | Experimental |'
  printf '%s\n' '| `scalar_max` | finite upper bound greater than `scalar_min` | Experimental |'
  printf '%s\n' '| `scalar_levels` | integer from 2 through 256 | Experimental |'
  printf '%s\n' '| `pq_subvector_dimensions` | positive divisor of vector dimensions | Experimental |'

  printf '\n%s\n\n' '## `pgcontext_ivfflat` Index Reloptions'
  printf '%s\n' '| Option | Accepted shape | Lifecycle |'
  printf '%s\n' '|---|---|---|'
  printf '%s\n' '| `lists` | integer from 1 through 32768 | Trained centroid-list count |'
  printf '%s\n' '| `quantization` | `none`, `sq8`, or `pq` | Candidate posting codec; exact source rerank is mandatory |'
  printf '%s\n' '| `pq_subvector_dimensions` | positive divisor of vector dimensions | Used when `quantization = pq` |'

  printf '\n%s\n\n' '## HNSW GUCs'
  printf '%s\n' '| Setting | Default | Lifecycle |'
  printf '%s\n' '|---|---:|---|'
  printf '%s\n' '| `pgcontext.hnsw_m` | `16` | Experimental HNSW serving policy |'
  printf '%s\n' '| `pgcontext.hnsw_ef_construction` | `64` | Experimental HNSW build policy |'
  printf '%s\n' '| `pgcontext.hnsw_ef_search` | `32` | Experimental HNSW search policy |'
  printf '%s\n' '| `pgcontext.hnsw_candidate_budget` | `32` | Experimental filtered/iterative policy |'
  printf '%s\n' '| `pgcontext.hnsw_iterative_expansion_limit` | `10000` | Experimental bounded expansion policy |'
  printf '%s\n' '| `pgcontext.hnsw_recall_threshold` | `0.95` | Experimental recall-health policy |'

  printf '\n%s\n\n' '## IVFFlat GUCs'
  printf '%s\n' '| Setting | Default | Lifecycle |'
  printf '%s\n' '|---|---:|---|'
  printf '%s\n' '| `pgcontext.ivfflat_probes` | `1` | Initial centroid-list count |'
  printf '%s\n' '| `pgcontext.ivfflat_max_probes` | `32768` | Maximum lists visited by iterative widening |'
  printf '%s\n' '| `pgcontext.ivfflat_candidate_budget` | `100000` | Hard posting/delta work ceiling |'
  printf '%s\n' '| `pgcontext.ivfflat_iterative_scan` | `off` | `off`, `strict_order`, or `relaxed_order` |'
  printf '%s\n' '| `pgcontext.ivfflat_build_parallel_workers` | `1` | Native PostgreSQL parallel assignment workers, maximum 16 |'

  printf '\n%s\n\n' '## Lexical GUCs'
  printf '%s\n' '| Setting | Default | Lifecycle |'
  printf '%s\n' '|---|---:|---|'
  printf '%s\n' '| `pgcontext.lexical_candidate_budget` | `1000` | Stable bounded lexical/fuzzy index probe allowance, maximum 10000 |'
} >"${tmp}"

for option in quantization scalar_min scalar_max scalar_levels pq_subvector_dimensions; do
  if ! grep -Fq "c\"${option}\"" "${OPTIONS_SOURCE}"; then
    echo "documented HNSW reloption is not registered: ${option}" >&2
    exit 1
  fi
done

for option in lists quantization pq_subvector_dimensions; do
  if ! grep -Fq "c\"${option}\"" "${IVFFLAT_OPTIONS_SOURCE}"; then
    echo "documented IVFFlat reloption is not registered: ${option}" >&2
    exit 1
  fi
done
for fragment in \
  'MAX_LISTS: i32 = 32_768;' \
  'c"none".as_ptr()' 'c"sq8".as_ptr()' 'c"pq".as_ptr()'; do
  if ! grep -Fq "${fragment}" "${IVFFLAT_OPTIONS_SOURCE}"; then
    echo "documented IVFFlat reloption rule is not source-backed: ${fragment}" >&2
    exit 1
  fi
done

awk '
  /let parse_elements = \[/ { in_options = 1; next }
  in_options && /^    \];/ { in_options = 0 }
  in_options && match($0, /c"[^"]+"/) {
    option = substr($0, RSTART + 2, RLENGTH - 3)
    print option
  }
' "${OPTIONS_SOURCE}" | sort -u >"${source_reloptions}"
printf '%s\n' quantization scalar_min scalar_max scalar_levels \
  pq_subvector_dimensions | sort -u >"${documented_reloptions}"
if ! diff -u "${documented_reloptions}" "${source_reloptions}" >&2; then
  echo "HNSW reloption inventory is not exhaustive" >&2
  exit 1
fi

awk '
  /static mut HNSW_QUANTIZATION_MEMBERS/ { in_members = 1; next }
  in_members && /^\];/ { in_members = 0 }
  in_members && match($0, /c"[^"]+"/) {
    member = substr($0, RSTART + 2, RLENGTH - 3)
    print member
  }
' "${OPTIONS_SOURCE}" | sort -u >"${source_quantization}"
printf '%s\n' none scalar sq8 pq binary | sort -u >"${documented_quantization}"
if ! diff -u "${documented_quantization}" "${source_quantization}" >&2; then
  echo "HNSW quantization enum inventory is not exhaustive" >&2
  exit 1
fi

for fragment in \
  'HNSW_SCALAR_MIN_LEVELS: i32 = 2;' \
  'HNSW_SCALAR_MAX_LEVELS: i32 = 256;' \
  'c"none".as_ptr()' 'c"scalar".as_ptr()' 'c"sq8".as_ptr()' 'c"pq".as_ptr()' 'c"binary".as_ptr()' \
  'scalar_min and scalar_max must be finite' \
  'scalar_min must be less than scalar_max' \
  'pq_subvector_dimensions must be positive when quantization is pq'; do
  if ! grep -Fq "${fragment}" "${OPTIONS_SOURCE}"; then
    echo "documented HNSW reloption rule is not source-backed: ${fragment}" >&2
    exit 1
  fi
done

for guc in \
  pgcontext.ivfflat_probes pgcontext.ivfflat_max_probes \
  pgcontext.ivfflat_candidate_budget pgcontext.ivfflat_iterative_scan \
  pgcontext.ivfflat_build_parallel_workers; do
  if ! grep -Fq "c\"${guc}\"" "${SETTINGS_SOURCE}"; then
    echo "documented IVFFlat GUC is not registered: ${guc}" >&2
    exit 1
  fi
done

for fragment in \
  'DEFAULT_IVFFLAT_PROBES: usize = 1;' \
  'MAX_IVFFLAT_LISTS: usize = 32_768;' \
  'DEFAULT_IVFFLAT_CANDIDATE_BUDGET: usize = 100_000;'; do
  if ! grep -Fq "${fragment}" "${POLICY_SOURCE}"; then
    echo "documented IVFFlat GUC default is not source-backed: ${fragment}" >&2
    exit 1
  fi
done

for guc in pgcontext.lexical_candidate_budget; do
  if ! grep -Fq "c\"${guc}\"" "${SETTINGS_SOURCE}"; then
    echo "documented lexical GUC is not registered: ${guc}" >&2
    exit 1
  fi
done

for fragment in \
  'DEFAULT_LEXICAL_CANDIDATE_BUDGET_I32: i32 = 1_000;' \
  'MAX_LEXICAL_CANDIDATE_BUDGET_I32: i32 = policy_usize_to_i32(MAX_RECALL_CHECK_POINT_IDS);'; do
  if ! grep -Fq "${fragment}" "${SETTINGS_SOURCE}"; then
    echo "documented lexical GUC default is not source-backed: ${fragment}" >&2
    exit 1
  fi
done

for guc in \
  pgcontext.hnsw_m pgcontext.hnsw_ef_construction pgcontext.hnsw_ef_search \
  pgcontext.hnsw_candidate_budget pgcontext.hnsw_iterative_expansion_limit \
  pgcontext.hnsw_recall_threshold; do
  if ! grep -Fq "c\"${guc}\"" "${SETTINGS_SOURCE}"; then
    echo "documented HNSW GUC is not registered: ${guc}" >&2
    exit 1
  fi
done


for fragment in \
  'DEFAULT_HNSW_M: usize = 16;' \
  'DEFAULT_HNSW_EF_CONSTRUCTION: usize = 64;' \
  'DEFAULT_HNSW_EF_SEARCH: usize = 32;' \
  'DEFAULT_HNSW_CANDIDATE_BUDGET: usize = DEFAULT_HNSW_EF_SEARCH;' \
  'MAX_RECALL_CHECK_POINT_IDS: usize = 10_000;' \
  'DEFAULT_HNSW_RECALL_THRESHOLD: f64 = 0.95;'; do
  if ! grep -Fq "${fragment}" "${POLICY_SOURCE}"; then
    echo "documented HNSW GUC default is not source-backed: ${fragment}" >&2
    exit 1
  fi
done

if "${CHECK_ONLY}"; then
  if [[ ! -f "${OUTPUT}" ]]; then
    echo "missing generated SQL object inventory: ${OUTPUT}" >&2
    exit 1
  fi
  diff -u "${OUTPUT}" "${tmp}"
else
  cp "${tmp}" "${OUTPUT}"
fi
