COLITUR  := colitur
PREFIX   ?= $(HOME)/.local
BINDIR   := $(PREFIX)/bin
MANDIR   := $(PREFIX)/share/man/man1
# Section 5 is file formats: the overlay format is a thing a user AUTHORS,
# not a command they run, so it belongs beside fstab(5) rather than in man1.
MAN5DIR  := $(PREFIX)/share/man/man5

# The binary locates its calendar data relative to its own path
# (<prefix>/share/colitur/ef), so `dune install` -- not a hand-rolled copy --
# is what places the four .sexp files where a running colitur will look for
# them. See bin/main.ml's own [data_dir] for the full resolution order.
# Task 13 (schema/dune, templates/dune) extends the same `dune install`
# mechanism to <prefix>/share/colitur/schema/ (bin/main.ml's own
# [schema_path], read by `colitur publish`) and <prefix>/share/colitur/
# templates/ (a convenience install -- `--template` takes a plain file path,
# nothing in colitur probes this location the way [data_dir]/[schema_path] do).
#
# dune needs the project-local opam switch on PATH. Every recipe that invokes
# dune goes through this, so `make` works from a plain shell with no
# `eval $(opam env)` first -- which is the whole point of having a Makefile
# over documenting the dune commands.
DUNE := opam exec --

.PHONY: help build test check check-schema check-templates check-citations install uninstall reinstall clean fmt man doc release
help: ## show this help
	@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed -E 's/:.*## /\t/' | sort

build: ## build the binary
	$(DUNE) dune build

test: ## fast suite (~5s): properties sample 200 years
	$(DUNE) dune test

check: ## full gate (~2min): every year 1583-9999, not a sample
	COLITUR_EXHAUSTIVE_SWEEP=1 $(DUNE) dune test --force

check-schema: build ## validate emitted XML against schema/colitur-v1.xsd (needs xmllint; skipped if absent)
	@if command -v xmllint >/dev/null 2>&1; then \
	  opam exec -- dune exec colitur -- emit --format xml --from 2027 --to 2027 > /tmp/colitur-check.xml && \
	  xmllint --noout --schema schema/colitur-v1.xsd /tmp/colitur-check.xml && \
	  echo "xml: valid against schema/colitur-v1.xsd"; \
	else \
	  echo "SKIPPED: xmllint not installed -- XML is emitted but NOT schema-validated"; \
	fi

# Typesets every shipped template through the real tool that would typeset it
# for a reader, not merely through this program's own renderer. The golden
# tests already prove `colitur table` PRODUCES the expected LaTeX/groff/HTML
# text; only this proves that text actually TYPESETS -- a template can render
# byte-for-byte as pinned and still be malformed LaTeX or groff. Each tool is
# genuinely optional (neither is in colitur's own frozen deps), so absence is
# printed LOUDLY as SKIPPED rather than silently treated as a pass -- a
# silent skip reads as a pass, which is the whole failure mode this guards
# against.
check-templates: build ## typeset every shipped template (needs pdflatex/groff; skipped if absent)
	@ok=1; \
	if command -v pdflatex >/dev/null 2>&1; then \
	  for t in ordo grid; do \
	    opam exec -- dune exec colitur -- table --year 2027 --template templates/ef/$$t.tex > /tmp/$$t.tex && \
	    (cd /tmp && pdflatex -halt-on-error -interaction=nonstopmode $$t.tex >/dev/null) && \
	    echo "pdflatex: $$t.tex OK" || { echo "pdflatex: $$t.tex FAILED"; ok=0; }; \
	  done; \
	else echo "SKIPPED: pdflatex not installed -- LaTeX templates render but are NOT typeset"; fi; \
	if command -v groff >/dev/null 2>&1; then \
	  for t in ordo grid; do \
	    gflags=""; \
	    [ "$$t" = grid ] && gflags="-P-pa4l"; \
	    if opam exec -- dune exec colitur -- table --year 2027 --template templates/ef/$$t.ms \
	         > /tmp/$$t.ms 2>/tmp/$$t-render.err; then \
	      groff -ms -t -Tpdf $$gflags /tmp/$$t.ms > /tmp/$$t-ms.pdf 2>/tmp/$$t-ms.warnings; \
	      if [ -s /tmp/$$t-ms.warnings ]; then \
	        echo "groff: $$t.ms FAILED (groff exits 0 on a tbl/troff warning -- this target"; \
	        echo "  treats ANY stderr output as a failure, since a warning here has meant"; \
	        echo "  silently lost table columns before):"; \
	        cat /tmp/$$t-ms.warnings; ok=0; \
	      else \
	        echo "groff: $$t.ms OK"; \
	      fi; \
	    else \
	      echo "groff: $$t.ms FAILED (colitur table render):"; cat /tmp/$$t-render.err; ok=0; \
	    fi; \
	  done; \
	else echo "SKIPPED: groff not installed -- groff templates render but are NOT typeset"; fi; \
	test $$ok -eq 1

# lang/la.ini's own discipline: every celebration/season/rank name cites the
# docs/research/LT.txt line it was transcribed from, so a claim can be
# checked, not just trusted. tools/check_citations.py re-derives that check
# mechanically: for every "LT.txt:<n>" citation outside a PATTERN-marked
# block, it confirms a +-2-line window around line n actually contains the
# Latin text the citation claims -- see that script's own docstring for the
# exact rule and its known limits (a heuristic, not a proof). docs/ is
# gitignored, so a fresh clone has no docs/research/LT.txt at all: the same
# "SKIPPED, loudly, exit 0" discipline check-schema/check-templates already
# use above -- a silent skip reads as a pass, which this project has hit
# the cost of before.
check-citations: ## verify lang/la.ini's LT.txt:<n> citations (needs docs/research/LT.txt, gitignored; SKIPPED if absent)
	@if command -v python3 >/dev/null 2>&1; then \
	  python3 tools/check_citations.py; \
	else \
	  echo "SKIPPED: python3 not installed -- citations NOT verified this run"; \
	fi

install: build ## install binary, calendar data, templates, schema and man pages into PREFIX (default ~/.local)
	$(DUNE) dune install --prefix $(PREFIX)
	@mkdir -p $(MANDIR)
	install -m 644 man/colitur.1 $(MANDIR)/colitur.1
	@mkdir -p $(MAN5DIR)
	install -m 644 man/colitur-overlay.5 $(MAN5DIR)/colitur-overlay.5
	install -m 644 man/colitur-templates.5 $(MAN5DIR)/colitur-templates.5
	@echo "installed $(BINDIR)/$(COLITUR), data in $(PREFIX)/share/colitur/{ef,templates,schema}, man pages in $(MANDIR) and $(MAN5DIR)"
	@command -v $(COLITUR) >/dev/null 2>&1 || \
	  echo "note: $(BINDIR) is not on PATH -- add it, or run $(BINDIR)/$(COLITUR) directly"

uninstall: ## remove everything install put into PREFIX
	-$(DUNE) dune uninstall --prefix $(PREFIX)
	rm -f $(MANDIR)/colitur.1 $(MAN5DIR)/colitur-overlay.5 $(MAN5DIR)/colitur-templates.5
	@echo "removed $(COLITUR) from $(PREFIX)"

reinstall: uninstall install ## uninstall then install (the installed copy is a snapshot, not a link)

man: ## preview the man pages
	man -l man/colitur.1
	man -l man/colitur-overlay.5
	man -l man/colitur-templates.5

doc: ## lint the man pages (groff warnings; silence means clean)
	groff -man -Tutf8 -ww -z man/colitur.1
	groff -man -Tutf8 -ww -z man/colitur-overlay.5
	groff -man -Tutf8 -ww -z man/colitur-templates.5

fmt: ## format the OCaml sources
	$(DUNE) dune build @fmt --auto-promote

clean: ## remove build artifacts
	$(DUNE) dune clean

# Mirrors lectio's own release target, including its refusals: no release from
# a dirty tree, no release without a CHANGELOG line, and no release whose
# version bump silently failed to apply. The version lives in TWO places
# (dune-project, which feeds colitur.opam, and bin/main.ml's own constant,
# which is what --version prints), so both are rewritten and both are
# re-checked -- a release that bumped one and not the other would ship a
# binary disagreeing with its own package metadata.
release: ## cut a release: make release VERSION=0.1.0  (add its CHANGELOG line first)
	@test -n "$(VERSION)" || { echo "set VERSION=X.Y.Z" >&2; exit 2; }
	@echo "$(VERSION)" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$$' || { echo "VERSION must be X.Y.Z" >&2; exit 2; }
	@test -z "$$(git status --porcelain)" || { echo "working tree not clean; commit or stash first" >&2; exit 2; }
	@grep -q '## \[$(VERSION)\]' CHANGELOG.md || { echo "add a one-line CHANGELOG.md entry under '## [$(VERSION)]' first" >&2; exit 2; }
	@sed -i 's/^(version .*)$$/(version $(VERSION))/' dune-project
	@sed -i 's/^let version = ".*"/let version = "$(VERSION)"/' bin/main.ml
	@grep -q '^(version $(VERSION))$$' dune-project || { echo "dune-project version bump failed" >&2; exit 2; }
	@grep -q '^let version = "$(VERSION)"' bin/main.ml || { echo "bin/main.ml version bump failed" >&2; exit 2; }
	$(MAKE) check
	@test "$$($(DUNE) dune exec --no-build colitur -- --version)" = "$(VERSION)" || \
	  { echo "the built binary does not report $(VERSION)" >&2; exit 2; }
	@git add dune-project colitur.opam bin/main.ml CHANGELOG.md
	@# Only commit if the bump actually changed something. Re-running release
	@# after a failed gate -- or cutting a version whose files are already
	@# correct, which is exactly how the first tag went -- leaves nothing
	@# staged, and `git commit` would abort the whole target on "nothing to
	@# commit" despite everything being in order.
	@git diff --cached --quiet || git commit -m "release: v$(VERSION)"
	@git rev-parse -q --verify "refs/tags/v$(VERSION)" >/dev/null && \
	  { echo "tag v$(VERSION) already exists; delete it first to re-cut" >&2; exit 2; } || true
	git tag -a "v$(VERSION)" -m "colitur $(VERSION)"
	@echo "tagged v$(VERSION). publish with: git push origin main v$(VERSION)"
