#!/bin/sh

# Verify that the exhaustive manual reference follows the public TeX surface
# and that release, scientific-scope, and side-effect contracts remain visible.
# scripts/test.sh separately compiles every executable example and the manual.

set -eu

fail()
{
    printf 'error: %s\n' "$*" >&2
    exit 1
}

need_command()
{
    command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}

need_command awk
need_command cat
need_command cp
need_command dirname
need_command grep
need_command mktemp
need_command rm
need_command sort
need_command tr

SCRIPT_DIR=$(CDPATH= cd -P "$(dirname "$0")" && pwd) \
    || fail "could not determine the script directory"
PROJECT_DIR=$(CDPATH= cd -P "$SCRIPT_DIR/.." && pwd) \
    || fail "could not determine the project directory"
TEX_SOURCE=$PROJECT_DIR/luacoolprop.tex
AUTONODE_SOURCE=$PROJECT_DIR/pgflibrarypgfplots.autonode.code.tex
TEX_REFERENCE=$PROJECT_DIR/docs/luacoolprop-tex-reference.tex
AUTONODE_REFERENCE=$PROJECT_DIR/docs/pgfplots-autonode-reference.tex
TUTORIAL_REFERENCE=$PROJECT_DIR/docs/r717-teaching-tutorial.tex
ADVANCED_TUTORIAL_REFERENCE=$PROJECT_DIR/docs/r134a-air-conditioner-tutorial.tex
MANUAL=$PROJECT_DIR/luacoolprop-manual.tex
README=$PROJECT_DIR/README.md
LUA_SOURCE=$PROJECT_DIR/luacoolprop.lua
STYLE_SOURCE=$PROJECT_DIR/luacoolprop.sty
RELEASE_SCRIPT=$PROJECT_DIR/scripts/package-release.sh

for required_file in "$TEX_SOURCE" "$AUTONODE_SOURCE" "$TEX_REFERENCE" \
    "$AUTONODE_REFERENCE" "$TUTORIAL_REFERENCE" \
    "$ADVANCED_TUTORIAL_REFERENCE" "$MANUAL" "$README" "$LUA_SOURCE" \
    "$STYLE_SOURCE" "$RELEASE_SCRIPT"; do
    [ -f "$required_file" ] || fail "required file not found: $required_file"
done

TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/luacoolprop-doc-check.XXXXXX") \
    || fail "could not create a temporary directory"
cleanup()
{
    rm -rf "$TEMP_DIR"
}
trap cleanup 0
trap 'exit 1' HUP INT TERM

COMMANDS=$TEMP_DIR/commands
GLOBAL_KEYS=$TEMP_DIR/global-keys
PGFPLOTS_KEYS=$TEMP_DIR/pgfplots-keys
PH_KEYS=$TEMP_DIR/ph-keys
PROCESS_KEYS=$TEMP_DIR/process-keys
PV_KEYS=$TEMP_DIR/pv-keys
PV_PROCESS_KEYS=$TEMP_DIR/pv-process-keys
TS_KEYS=$TEMP_DIR/ts-keys
TS_PROCESS_KEYS=$TEMP_DIR/ts-process-keys
HS_KEYS=$TEMP_DIR/hs-keys
HS_PROCESS_KEYS=$TEMP_DIR/hs-process-keys
PT_KEYS=$TEMP_DIR/pt-keys
PT_PROCESS_KEYS=$TEMP_DIR/pt-process-keys
AUTONODE_LABEL_KEYS=$TEMP_DIR/autonode-label-keys
AUTONODE_AXIS_KEYS=$TEMP_DIR/autonode-axis-keys
MISSING=$TEMP_DIR/missing
: >"$MISSING"

# Public LuaCoolProp commands are definitions whose names start with LCP and do
# not contain the private @ marker.  The result constants are included because
# they are ordinary public definitions matching the same rule.
awk '
  /^\\(def|let)\\LCP[A-Za-z]/ {
    line=$0
    sub(/^\\(def|let)\\/, "", line)
    sub(/[^A-Za-z].*$/, "", line)
    print line
  }
' "$TEX_SOURCE" >"$COMMANDS.raw"
sort -u "$COMMANDS.raw" >"$COMMANDS"

extract_luacoolprop_family()
{
    family=$1
    output=$2
    awk -v prefix="/luacoolprop/$family/" '
      index($0, prefix) {
        line=$0
        sub(/^[[:space:]]*/, "", line)
        if (index(line, prefix) != 1) next
        sub("^" prefix, "", line)
        sub(/\/\..*$/, "", line)
        sub(/\/(jkg|kjkg|pa|kpa|mpa|bar|m3kg|lkg|jkgk|kjkgk|kelvin|celsius)$/, "", line)
        if (line != "" && line !~ /^\./) print line
      }
    ' "$TEX_SOURCE" >"$output.raw"
    sort -u "$output.raw" >"$output"
}

extract_luacoolprop_family "diagram/common" "$TEMP_DIR/diagram-common"
extract_luacoolprop_family "process/common" "$TEMP_DIR/process-common"
for diagram_type in PH PV TS HS PT; do
    lower_type=$(printf '%s' "$diagram_type" | tr '[:upper:]' '[:lower:]')
    extract_luacoolprop_family "$lower_type diagram" \
        "$TEMP_DIR/$lower_type-legacy-diagram"
    extract_luacoolprop_family "diagram/$diagram_type" \
        "$TEMP_DIR/$lower_type-canonical-diagram"
    cat "$TEMP_DIR/diagram-common" \
        "$TEMP_DIR/$lower_type-legacy-diagram" \
        "$TEMP_DIR/$lower_type-canonical-diagram" \
        | sort -u >"$TEMP_DIR/$lower_type-diagram-all"

    extract_luacoolprop_family "$lower_type process" \
        "$TEMP_DIR/$lower_type-legacy-process"
    extract_luacoolprop_family "process/$diagram_type" \
        "$TEMP_DIR/$lower_type-canonical-process"
    cat "$TEMP_DIR/process-common" \
        "$TEMP_DIR/$lower_type-legacy-process" \
        "$TEMP_DIR/$lower_type-canonical-process" \
        | sort -u >"$TEMP_DIR/$lower_type-process-all"
done
cp "$TEMP_DIR/ph-diagram-all" "$PH_KEYS"
cp "$TEMP_DIR/ph-process-all" "$PROCESS_KEYS"
cp "$TEMP_DIR/pv-diagram-all" "$PV_KEYS"
cp "$TEMP_DIR/pv-process-all" "$PV_PROCESS_KEYS"
cp "$TEMP_DIR/ts-diagram-all" "$TS_KEYS"
cp "$TEMP_DIR/ts-process-all" "$TS_PROCESS_KEYS"
cp "$TEMP_DIR/hs-diagram-all" "$HS_KEYS"
cp "$TEMP_DIR/hs-process-all" "$HS_PROCESS_KEYS"
cp "$TEMP_DIR/pt-diagram-all" "$PT_KEYS"
cp "$TEMP_DIR/pt-process-all" "$PT_PROCESS_KEYS"

# The package-wide family and the two PGFPlots aliases live outside the diagram
# families, so collect them independently.  The unknown-key handler is an
# implementation detail and deliberately starts with a dot after extraction.
awk '
  /^[[:space:]]*\/luacoolprop\/[^\/]+\/\./ {
    line=$0
    sub(/^.*\/luacoolprop\//, "", line)
    sub(/\/\..*$/, "", line)
    if (line != "" && line !~ /^\./ && line != "ph diagram" && line != "ph process" && line != "pv diagram" && line != "pv process" && line != "ts diagram" && line != "ts process" && line != "hs diagram" && line != "hs process" && line != "pt diagram" && line != "pt process") print line
  }
' "$TEX_SOURCE" >"$GLOBAL_KEYS.raw"
sort -u "$GLOBAL_KEYS.raw" >"$GLOBAL_KEYS"

awk '
  /^[[:space:]]*(luacoolprop fluid|lcp fluid)\/\./ {
    line=$0
    sub(/^[[:space:]]*/, "", line)
    sub(/\/\..*$/, "", line)
    print line
  }
' "$TEX_SOURCE" >"$PGFPLOTS_KEYS.raw"
sort -u "$PGFPLOTS_KEYS.raw" >"$PGFPLOTS_KEYS"

# Per-label keys are relative to /pgfplots/autonode/.cd.  Stop before the
# axis-level pgfplotsset block and collapse choice subkeys to their parent key.
awk '
  /User keys for \\pgfplotsautonode/ { inside=1; next }
  /pgfplots axis-level keys/ { inside=0 }
  inside && /^[[:space:]]*[A-Za-z]/ && /\/\./ {
    line=$0
    sub(/^[[:space:]]*/, "", line)
    sub(/\/\..*$/, "", line)
    sub(/\/(uniform|around preferred|around-preferred|adaptive)$/, "", line)
    if (line != "default") print line
  }
' "$AUTONODE_SOURCE" >"$AUTONODE_LABEL_KEYS.raw"
sort -u "$AUTONODE_LABEL_KEYS.raw" >"$AUTONODE_LABEL_KEYS"

# Axis choices use key/value/.code; retain only the parent axis key.
awk '
  /pgfplots axis-level keys/ { inside=1; next }
  inside && /^[[:space:]]*auto node / && /\/\./ {
    line=$0
    sub(/^[[:space:]]*/, "", line)
    sub(/\/\..*$/, "", line)
    sub(/\/(false|off|summary|verbose|trace|greedy|repair|local-search|exact-small|axis-aligned|oriented|warn|error|hide-low-priority|allow-minimal-overlap|uniform|around preferred|around-preferred|adaptive)$/, "", line)
    print line
  }
' "$AUTONODE_SOURCE" >"$AUTONODE_AXIS_KEYS.raw"
sort -u "$AUTONODE_AXIS_KEYS.raw" >"$AUTONODE_AXIS_KEYS"

check_names()
{
    kind=$1
    names=$2
    reference=$3
    while IFS= read -r name; do
        [ -n "$name" ] || continue
        if ! grep -Fq "$name" "$reference"; then
            printf '%s: %s\n' "$kind" "$name" >>"$MISSING"
        fi
    done <"$names"
}

check_names "command" "$COMMANDS" "$TEX_REFERENCE"
check_names "global key" "$GLOBAL_KEYS" "$TEX_REFERENCE"
check_names "PGFPlots key" "$PGFPLOTS_KEYS" "$TEX_REFERENCE"
check_names "PH key" "$PH_KEYS" "$TEX_REFERENCE"
check_names "PH process key" "$PROCESS_KEYS" "$TEX_REFERENCE"
check_names "PV key" "$PV_KEYS" "$TEX_REFERENCE"
check_names "PV process key" "$PV_PROCESS_KEYS" "$TEX_REFERENCE"
check_names "TS key" "$TS_KEYS" "$TEX_REFERENCE"
check_names "TS process key" "$TS_PROCESS_KEYS" "$TEX_REFERENCE"
check_names "HS key" "$HS_KEYS" "$TEX_REFERENCE"
check_names "HS process key" "$HS_PROCESS_KEYS" "$TEX_REFERENCE"
check_names "PT key" "$PT_KEYS" "$TEX_REFERENCE"
check_names "PT process key" "$PT_PROCESS_KEYS" "$TEX_REFERENCE"
check_names "autonode label key" "$AUTONODE_LABEL_KEYS" "$AUTONODE_REFERENCE"
check_names "autonode axis key" "$AUTONODE_AXIS_KEYS" "$AUTONODE_REFERENCE"

grep -Fq '\input{docs/luacoolprop-tex-reference.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/luacoolprop-tex-reference.tex' >>"$MISSING"
grep -Fq '\input{docs/pgfplots-autonode-reference.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/pgfplots-autonode-reference.tex' >>"$MISSING"
grep -Fq '\input{docs/r717-teaching-tutorial.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/r717-teaching-tutorial.tex' >>"$MISSING"
grep -Fq '\input{docs/r134a-air-conditioner-tutorial.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/r134a-air-conditioner-tutorial.tex' >>"$MISSING"
grep -Fq '\input{docs/butane-pv-tutorial.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/butane-pv-tutorial.tex' >>"$MISSING"
grep -Fq '\input{docs/r1234yf-heat-pump-tutorial.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/r1234yf-heat-pump-tutorial.tex' >>"$MISSING"
grep -Fq '\input{docs/methane-linde-tutorial.tex}' "$MANUAL" \
    || printf '%s\n' 'manual input: docs/methane-linde-tutorial.tex' >>"$MISSING"
grep -Fq '\makeindex[name=commands' "$MANUAL" \
    || printf '%s\n' 'manual index: commands' >>"$MISSING"
grep -Fq '\makeindex[name=keys' "$MANUAL" \
    || printf '%s\n' 'manual index: keys' >>"$MISSING"
grep -Fq '\begin{LCPShowcase}' "$TEX_REFERENCE" \
    || printf '%s\n' 'live example: LuaCoolProp' >>"$MISSING"
grep -Fq '\begin{LCPShowcase' "$AUTONODE_REFERENCE" \
    || printf '%s\n' 'live example: pgfplots-autonode' >>"$MISSING"

# Release and scientific-scope contracts are checked explicitly rather than by
# a loose name search. This prevents a complete-looking reference from silently
# omitting a safety-critical qualification.
grep -Fq 'v1.1.0' "$STYLE_SOURCE" \
    || printf '%s\n' 'release version: luacoolprop.sty' >>"$MISSING"
grep -Fq '_VERSION = "1.1.0"' "$LUA_SOURCE" \
    || printf '%s\n' 'release version: luacoolprop.lua' >>"$MISSING"
grep -Fq '# luacoolprop 1.1.0' "$README" \
    || printf '%s\n' 'release version: README.md' >>"$MISSING"
grep -Fq 'Christophe Jorssen' "$README" \
    || printf '%s\n' 'release maintainer: README.md' >>"$MISSING"
grep -Fq 'LPPL 1.3c or later' "$README" \
    || printf '%s\n' 'release license: README.md' >>"$MISSING"
grep -Fq 'quality_defined = false' "$LUA_SOURCE" \
    || printf '%s\n' 'critical-limit metadata: quality_defined' >>"$MISSING"
grep -Fq 'critical_limit = true' "$LUA_SOURCE" \
    || printf '%s\n' 'critical-limit metadata: critical_limit' >>"$MISSING"
grep -Fq 'quality\_defined=false' "$TEX_REFERENCE" \
    || printf '%s\n' 'critical-limit contract: TeX reference' >>"$MISSING"
grep -Fq 'require a pure fluid' "$LUA_SOURCE" \
    || printf '%s\n' 'scientific scope: pure fluids' >>"$MISSING"
grep -Fq 'set_reference_state' "$LUA_SOURCE" \
    || printf '%s\n' 'scientific scope: reference states' >>"$MISSING"
grep -Fq 'luacoolprop.tds.zip' "$RELEASE_SCRIPT" \
    || printf '%s\n' 'release archive: TDS output' >>"$MISSING"
grep -Fq '\usetikzlibrary{intersections}' "$TEX_SOURCE" \
    || printf '%s\n' 'runtime dependency: TikZ intersections' >>"$MISSING"

if grep -Fq '\pgfplotsset{compat=' "$TEX_SOURCE"; then
    printf '%s\n' 'global side effect: PGFPlots compat' >>"$MISSING"
fi
if grep -Fq '\pgfplotsset{every axis/.append style={auto node placement}}' \
    "$TEX_SOURCE"; then
    printf '%s\n' 'global side effect: autonode every axis' >>"$MISSING"
fi
if grep -Eiq 'release candidate|supporting development material|predate(s|d)? the structured API|internal placement prototype|historical flat|historical PH|future work|future diagram|later releases' \
    "$README" "$MANUAL" "$TEX_REFERENCE" "$AUTONODE_REFERENCE"; then
    printf '%s\n' 'public documentation: development-stage wording' >>"$MISSING"
fi

if [ -s "$MISSING" ]; then
    printf 'error: public API documentation coverage is incomplete:\n' >&2
    while IFS= read -r missing_entry; do
        printf '  - %s\n' "$missing_entry" >&2
    done <"$MISSING"
    exit 1
fi

printf 'Documentation coverage check passed.\n'
