# docgen — code to diagram, and to everything else the IR can feed.
#
# Derived from where this file sits, so the folder can be copied anywhere and
# renamed and still work. The logic lives in the Python, never here: every target
# is one line calling `python3 -m docgen <command>`.
#
#   make sync                      create .venv with every optional group (uv)
#   make book SRC=../station       the whole operation, measured at both ends
#   make run CONFIG=docgen.toml    every book a run file lists
#   make check                     prove docgen, on a tree it builds itself
#   make check BOOK=out/book/x     prove one book — its own level
#   make ir SRC=../station         extract -> out/ir.json  (one step, on its own)
#   make self                      docgen's book of itself, then check it
#   make doctor                    what this machine has
#
# Every step target still works alone — that is the property the book spine
# exists to preserve, not to replace. The steps compose by hand too:
#
#   python3 -m docgen extract python --root SRC -o ir.json
#   python3 -m docgen view ir.json --overview -o view.json
#   python3 -m docgen emit dot view.json -o graph.svg --theme dark

HERE   := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
PKG    := $(notdir $(HERE))
PARENT := $(patsubst %/,%,$(dir $(HERE)))
VENV_PY := $(HERE)/.venv/bin/python
# The synced environment when there is one (`make sync`), the system Python when
# there is not — so `make check` still works with nothing installed at all.
PY     ?= $(if $(wildcard $(VENV_PY)),$(VENV_PY),python3)
CLI    := PYTHONPATH=$(PARENT) $(PY) -m $(PKG)

OUT    ?= $(HERE)/out
SRC    ?=
SCHEMA ?=
OPENAPI ?=
HAR    ?=
STYLE  ?= lucid
THEME  ?=
SCALE  ?= 0.55
BOOK   ?=
SLUG   ?=
# NOT `LANG`: that is the shell's locale variable, so `?=` inherits
# en_US.UTF-8 from the environment and --reader rejects it.
READER ?= python
OVERLAY ?=
CONFIG ?= docgen.toml
ONLY   ?=
CHECK  ?=

comma  := ,
THEME_ARG := $(if $(THEME),--theme $(THEME))
STYLE_ARGS := --style $(STYLE) $(THEME_ARG)
SLUG_ARG  := $(if $(SLUG),--slug $(SLUG))
OVER_ARG  := $(if $(OVERLAY),--overlay $(OVERLAY))
ONLY_ARGS := $(foreach n,$(subst $(comma), ,$(ONLY)),--only $(n))

.PHONY: help sync lock book run check ir db code view graph index site minimap explore docs self doctor clean

help:  ## List every target
	@echo "docgen — static analysis of a tree, and the artifacts that fall out of it"
	@echo
	@grep -E '^[a-z-]+:.*?## .*$$' $(MAKEFILE_LIST) \
	  | awk 'BEGIN{FS=":.*?## "}{printf "  \033[1m%-10s\033[0m %s\n", $$1, $$2}'
	@echo
	@echo "  SRC=/path/to/tree     what to read           OUT=/path   where output goes"
	@echo "  SCHEMA=schema.json    a database instead     STYLE=lucid THEME=dark|lucid"
	@echo "  OPENAPI=spec.yaml     an API document        HAR=session.har  a recording"
	@echo "  BOOK=/path            where a book goes, and which book to check"
	@echo "  READER=python|code    ast, or tree-sitter    SLUG=name   what to call the book"
	@echo "  OVERLAY=overlay.json  hand-written notebook additions, re-applied every build"
	@echo "  CONFIG=docgen.toml    a run file             ONLY=a,b    just these books"
	@echo "  CHECK=1               with run: each book's own level after building it"
	@echo
	@echo "  Three levels of test, by what they assert about:"
	@echo "    make doctor              the machine. Never fails."
	@echo "    make check               docgen. Exits 1."
	@echo "    make check BOOK=<dir>    that book. Exits 1."

sync:  ## Create .venv with every optional group, from uv.lock
	@command -v uv >/dev/null || { echo "Error: uv is not installed — docgen still runs on the system python3" >&2; exit 1; }
	@cd $(HERE) && uv sync --all-groups

lock:  ## Re-resolve uv.lock after editing pyproject.toml
	@cd $(HERE) && uv lock

book:  ## SRC (or SCHEMA/OPENAPI/HAR) -> one operation, measured at both ends
	@test -n "$(SRC)$(SCHEMA)$(OPENAPI)$(HAR)" \
	  || { echo "Error: set SRC=/path/to/tree (or SCHEMA=, OPENAPI=, HAR=)" >&2; exit 1; }
	@$(CLI) book \
	  $(if $(SRC),--root "$(SRC)" --reader $(READER)) \
	  $(if $(SCHEMA),--schema "$(SCHEMA)") \
	  $(if $(OPENAPI),--openapi "$(OPENAPI)") \
	  $(if $(HAR),--har "$(HAR)") \
	  -o "$(if $(BOOK),$(BOOK),$(OUT)/book)" \
	  $(STYLE_ARGS) $(SLUG_ARG) $(OVER_ARG)

run:  ## CONFIG (a run file) -> every book it lists; ONLY=a,b for some
	@$(CLI) run "$(CONFIG)" $(ONLY_ARGS) $(if $(CHECK),--check)

check:  ## Prove docgen (or one book, with BOOK=<dir>)
	@$(CLI) check $(if $(BOOK),"$(BOOK)")

ir:  ## Extract SRC into OUT/ir.json
	@test -n "$(SRC)" || { echo "Error: set SRC=/path/to/tree" >&2; exit 1; }
	@$(CLI) extract python --root "$(SRC)" -o $(OUT)/ir.json
	@$(CLI) validate $(OUT)/ir.json

code:  ## Extract C#/TypeScript from SRC (needs the `code` group)
	@test -n "$(SRC)" || { echo "Error: set SRC=/path/to/tree" >&2; exit 1; }
	@$(CLI) extract code --root "$(SRC)" -o $(OUT)/ir.json
	@$(CLI) validate $(OUT)/ir.json

db:  ## Extract a graphgen-compatible SCHEMA into OUT/ir.json
	@test -n "$(SCHEMA)" || { echo "Error: set SCHEMA=/path/to/schema.json" >&2; exit 1; }
	@$(CLI) extract db --schema "$(SCHEMA)" -o $(OUT)/ir.json
	@$(CLI) validate $(OUT)/ir.json

view:  ## OUT/ir.json -> OUT/view.json, the default view for its source type
	@$(CLI) view $(OUT)/ir.json --overview -o $(OUT)/view.json

graph: view  ## OUT/view.json -> whatever its structure asks for
	@$(CLI) emit auto $(OUT)/view.json -o $(OUT) $(STYLE_ARGS)

index:  ## OUT/ir.json -> OUT/index.md and OUT/sidebar.json
	@$(CLI) emit index $(OUT)/ir.json -o $(OUT)/index.md
	@$(CLI) emit index $(OUT)/ir.json -o $(OUT)/sidebar.json

site: view  ## OUT/view.json -> a self-contained docs site in OUT/site
	@$(CLI) emit site $(OUT)/view.json -o $(OUT)/site $(STYLE_ARGS)
	@echo "  open $(OUT)/site/index.html"

minimap:  ## OUT/ir.json -> OUT/minimap.svg — what is where, read from the colours
	@$(CLI) emit minimap $(OUT)/ir.json -o $(OUT)/minimap.svg $(STYLE_ARGS) --scale $(SCALE)

explore: ## OUT/ir.json -> OUT/explore/ — navigate on one side, explore on the other
	@$(CLI) emit explore $(OUT)/ir.json -o $(OUT)/explore $(STYLE_ARGS) --scale $(SCALE)
	@echo "  open $(OUT)/explore/explore.html"

docs:  ## Regenerate the figures in docs/ — docgen documented by docgen
	@mkdir -p $(HERE)/docs/img
	@$(CLI) extract python --root $(HERE) -o /tmp/$(PKG)-docs.json >/dev/null
	@$(CLI) view /tmp/$(PKG)-docs.json --overview -o /tmp/$(PKG)-docs-view.json >/dev/null
	@$(CLI) emit dot /tmp/$(PKG)-docs-view.json -o $(HERE)/docs/img/architecture.svg -q
	@$(CLI) emit minimap /tmp/$(PKG)-docs.json -o $(HERE)/docs/img/minimap.svg --scale 0.5 --width 860
	@$(CLI) emit erd $(OUT)/ir.json -o $(HERE)/docs/img/erd.svg 2>/dev/null \
	  || echo "  (erd figure kept — needs a schema IR at $(OUT)/ir.json to refresh)"
	@PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG).emitters.site import VIEWER, _slots, _fill; \
from $(PKG).style import Style; import pathlib; \
pathlib.Path('$(HERE)/docs/viewer.html').write_text( \
  _fill(VIEWER.replace('__TITLE__', 'docgen docs'), _slots(Style.load('lucid'))))"
	@echo "  open $(HERE)/docs/index.html"

self:  ## docgen's book of the widest tree it can see, then check it
	@$(eval SELF_SRC := $(shell PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG) import reference; \
r = reference.root(); print(r if r else '$(HERE)')"))
	@echo "  self-hosting on $(SELF_SRC)"
	@$(MAKE) --no-print-directory book SRC=$(SELF_SRC) SLUG=self \
	  BOOK=$(OUT)/book/self OUT=$(OUT)
	@echo
	@$(MAKE) --no-print-directory check BOOK=$(OUT)/book/self

doctor:  ## Report whether this machine can run it
	@printf 'python   : %s ' '$(PY)'; $(PY) --version 2>&1 || echo MISSING
	@printf 'uv       : '; if ! command -v uv >/dev/null; then echo 'absent — fine; docgen runs on the system python3'; \
	  elif [ -x $(VENV_PY) ]; then echo "$$(uv --version), .venv synced"; \
	  else echo "$$(uv --version), .venv not synced — make sync for the optional groups"; fi
	@printf 'dot      : '; (dot -V 2>&1) || echo 'MISSING — sudo apt install graphviz (only to render)'
	@printf 'tree-sit : '; $(PY) -c 'import tree_sitter, tree_sitter_c_sharp, tree_sitter_typescript; print("ok — C# and TypeScript available")' 2>/dev/null || echo 'absent — Python only. make sync, or the `code` group'
	@printf 'lxml     : '; $(PY) -c 'import lxml; print("ok — theme harvesting available")' 2>/dev/null || echo 'absent — only used to harvest a theme'
	@printf 'yaml     : '; $(PY) -c 'import yaml; print("ok — needed only to read OpenAPI")' 2>/dev/null || echo 'absent — only used by the OpenAPI reader'
	@printf 'networkx : '; $(PY) -c 'import networkx; print(networkx.__version__ + " — for lab/ experiments")' 2>/dev/null || echo 'absent — only used in lab/'
	@printf 'reference: '; PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG) import reference; print(reference.describe())"
	@printf 'package  : %s (from %s)\n' '$(PKG)' '$(PARENT)'
	@printf 'styles   : '; PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG).style import Style; print(', '.join(Style.available()))"
	@PYTHONPATH=$(PARENT) $(PY) -c "import $(PKG).ir, $(PKG).emitters.dot, $(PKG).ops, $(PKG).cli" >/dev/null 2>&1 \
	  && echo 'import   : ok' || echo 'import   : FAILED — is the folder intact?'

clean:  ## Delete OUT. Nothing else is ever written to
	@rm -rf "$(OUT)" && echo "Removed $(OUT)"
