#!/bin/bash
# model-doc-check --- validate the BSPPFit API doc blocks against the BOSL2
# openscad-docsgen documentation standard (see FutureResearch.org
# section "BOSL2 documentation-standard conformance").
#
# Two gates, run in order, no images generated:
#   1. structure --- openscad-docsgen -n -S -r : --strict requires the
#      File/LibFile and Section headers; -r writes a machine report.
#   2. examples  --- openscad-docsgen -Tmf     : the canonical BOSL2 CI
#      check; -T executes every "// Example:" (with the file's
#      "// Includes:" prepended) but skips PNG rendering, proving the
#      examples still compile against the library.
# A non-zero exit from either gate means a documentation defect.
#
# Prerequisites:
#   - openscad-docsgen on PATH   (pip/pipx installs it under ~/.local/bin,
#     which this script adds to PATH below), actually RUNNABLE, not merely
#     present: a self-test below catches the case where the console-script
#     wrapper's own baked-in interpreter can no longer find the
#     openscad_docsgen module (typically after a Python version change),
#     which `command -v` alone would miss.
#   - openscad on PATH, runnable NON-INTERACTIVELY. docsgen drives it with
#     command-line options (e.g. "openscad -o out.png in.scad"); if your
#     "openscad" is a wrapper around an AppImage or a .app bundle it MUST
#     forward its arguments (end the wrapper with: exec .../OpenSCAD "$@"),
#     otherwise OpenSCAD launches its GUI and waits for a file to open.
#   - BOSL2 reachable as a directory named exactly "BOSL2" on OPENSCADPATH
#   - on a headless box, Xvfb (the script wraps itself in xvfb-run when no
#     DISPLAY is set)
#
# Usage: model-doc-check [extra openscad-docsgen args]
set -euo pipefail

# pip/pipx install console scripts (openscad-docsgen) under ~/.local/bin;
# put it on PATH so the tool is found without the caller exporting it.
case ":$PATH:" in
    *":$HOME/.local/bin:"*) : ;;
    *) PATH="$HOME/.local/bin:$PATH" ;;
esac
export PATH

ROOT=$(cd "$(dirname "$0")" && pwd)
# Source path is kept relative to ROOT (the script cd's there before
# running docsgen): docsgen resolves its per-file output against the
# DocsDirectory only when the source is given relatively. An absolute
# path makes it mis-place the generated Markdown.
SRC="bsppfit.scad"

# --- locate the library path so examples can resolve <BOSL2/...> and
#     <bsppfit.scad>. Honour a caller-supplied OPENSCADPATH; else guess.
if [ -z "${OPENSCADPATH:-}" ]; then
    for d in "$HOME/libraries" \
             "$HOME/.local/share/OpenSCAD/libraries" \
             "$HOME/Documents/OpenSCAD/libraries" \
             "$HOME/Library/Application Support/OpenSCAD/libraries"; do
        if [ -d "$d/BOSL2" ]; then OPENSCADPATH="$d"; break; fi
    done
fi
OPENSCADPATH="${OPENSCADPATH:-}:$ROOT"
export OPENSCADPATH

# --- tool checks
# Existence on PATH is necessary but not sufficient --- see model-docs
# for the full rationale and the project-author case (2026-09-05) this
# self-test exists to catch: a console-script wrapper whose own baked-
# in interpreter has lost access to the module it wraps, most commonly
# after a Python version change. `command -v` alone would not catch
# this.
for t in openscad-docsgen openscad; do
    command -v "$t" >/dev/null 2>&1 || {
        echo "model-doc-check: error: '$t' not found on PATH" >&2; exit 2; }
done
if ! DOCSGEN_SELFTEST=$(openscad-docsgen --help 2>&1); then
    DOCSGEN_PATH=$(command -v openscad-docsgen)
    echo "model-doc-check: error: 'openscad-docsgen' is on PATH but failed to run." >&2
    echo "model-doc-check: its own error output:" >&2
    echo "$DOCSGEN_SELFTEST" | sed 's/^/model-doc-check:   /' >&2
    echo "model-doc-check: this usually means the console-script wrapper's" >&2
    echo "model-doc-check: own interpreter (baked into its shebang line at" >&2
    echo "model-doc-check: install time) can no longer find the openscad_docsgen" >&2
    echo "model-doc-check: module --- typically after a Python version change." >&2
    echo "model-doc-check: found at: $DOCSGEN_PATH" >&2
    echo "model-doc-check: its shebang (the interpreter or venv it targets):" >&2
    echo "model-doc-check:   $(head -1 "$DOCSGEN_PATH" 2>/dev/null || echo '(could not read it)')" >&2
    echo "model-doc-check: (a pipx-managed install's shebang contains pipx/venvs/;" >&2
    echo "model-doc-check: a plain pip install shows a system or --user python3." >&2
    echo "model-doc-check: Printed directly above, not left for you to re-check in a" >&2
    echo "model-doc-check: separate shell: this script prepends ~/.local/bin to its" >&2
    echo "model-doc-check: own PATH if not already present (pip/pipx's usual install" >&2
    echo "model-doc-check: location), so a plain 'command -v openscad-docsgen' run in" >&2
    echo "model-doc-check: your own shell can genuinely fail even though this script" >&2
    echo "model-doc-check: finds it fine --- that is not a contradiction, it is this" >&2
    echo "model-doc-check: PATH prepending working as intended; see PATH in the man" >&2
    echo "model-doc-check: page's ENVIRONMENT section.)" >&2
    echo "model-doc-check: To fix, reinstall with whichever tool manages it:" >&2
    echo "model-doc-check:   pipx install --force openscad-docsgen" >&2
    echo "model-doc-check:   python3 -m pip install --user --force-reinstall openscad-docsgen" >&2
    exit 2
fi
case ":$OPENSCADPATH:" in
    *BOSL2*) : ;;   # a BOSL2 dir is somewhere on the path (best-effort)
esac

# --- headless: wrap in a virtual X server if there is no display
RUN=""
if [ -z "${DISPLAY:-}" ] && command -v xvfb-run >/dev/null 2>&1; then
    RUN="xvfb-run -a"
fi

# --- generated outputs (the rc's DocsDirectory and the report) are build
#     artifacts; remove them on exit unless the docs dir already existed.
DOCSDIR=$(awk -F': *' '/^DocsDirectory:/{print $2; exit}' \
          "$ROOT/.openscad_docsgen_rc" 2>/dev/null)
DOCSDIR=${DOCSDIR:-docs}
PREEXIST=0; [ -e "$ROOT/$DOCSDIR" ] && PREEXIST=1
WORK=$(mktemp -d "${TMPDIR:-/tmp}/bsppfit-model-doc-check.XXXXXX")
cleanup() {
    rm -rf "$WORK" "$ROOT/docsgen_report.json"
    [ "$PREEXIST" = 0 ] && rm -rf "${ROOT:?}/$DOCSDIR"
    # defensive: docsgen has been seen to drop a sidecar .scad.md / images
    # dir next to the source under some path forms; these are never source.
    rm -f  "$ROOT"/*.scad.md 2>/dev/null
    rm -rf "$ROOT/images"    2>/dev/null
    return 0
}
trap cleanup EXIT
cd "$ROOT"

echo "model-doc-check: 1/2 structure (strict File/LibFile/Section headers)"
$RUN openscad-docsgen -n -S -r "$SRC" "$@"
if [ -s "$ROOT/docsgen_report.json" ] && \
   ! grep -q '^\[\]$' "$ROOT/docsgen_report.json" 2>/dev/null; then
    echo "model-doc-check: structure report has findings:" >&2
    cat "$ROOT/docsgen_report.json" >&2
    exit 1
fi

echo "model-doc-check: 2/2 examples (compile every // Example:)"
$RUN openscad-docsgen -Tmf "$SRC" "$@"

echo "model-doc-check: PASS --- doc blocks conform to the openscad-docsgen standard."
