# ============================================================================
# ADK-FLUENT DEVELOPMENT WORKFLOW
# ============================================================================
#
#   just setup      → First-time setup: install deps + pre-commit hooks
#   just all        → Full pipeline: scan → seed → generate → docs
#   just scan       → Introspect installed ADK, produce manifest.json
#   just seed       → Generate seed.toml from manifest.json
#   just generate   → Combine seed.toml + manifest.json → code + stubs + tests + lint
#   just lint       → Run ruff check + format check (hand-written files only)
#   just fmt        → Auto-format hand-written files (ruff)
#   just fmt-changed → Auto-format only changed hand-written files (fast)
#   just check-gen  → Verify generated files are up-to-date (idempotency)
#   just docs       → Generate all documentation (includes llms.txt + editor rules)
#   just llms       → Generate llms.txt + editor rules only
#   just test       → Run all tests
#   just typecheck  → Run pyright on generated stubs
#   just diff       → Show what changed since last scan
#   just clean      → Remove generated files
#
#   --- CI PARITY ---
#   just preflight  → Run pre-commit hooks (mirrors CI lint exactly)
#   just ci         → Full local CI: preflight + check-gen + test
#
#   --- 100x DX COMMANDS ---
#   just watch      → Auto-run generate+test on changes
#   just repl       → Pre-loaded IPython playground
#   just worktree N → Create isolated worktree + install deps
#   just add-cookbook "Name" → Scaffold new example
#
# First-time setup:
#   just setup
#
# Architecture note:
#   Generated files (agent.py, workflow.py, etc.) are owned by `just generate`.
#   Hand-written files (_base.py, _context.py, etc.) are owned by developers.
#   Formatters and pre-commit ONLY touch hand-written files.
#   `just generate` formats its own output as part of the generation pipeline.
#   See .gitattributes for the full generated file list.
#

# --- First-time setup ---
setup:
    @echo "Installing dependencies..."
    @uv sync --all-extras
    @echo "Installing pre-commit hooks..."
    @uv run pre-commit install
    @echo ""
    @echo "Setup complete. Pre-commit hooks will auto-format on every commit."
    @echo "Run 'just all' to generate code, or 'just fmt' to format existing files."

SEED          := "seeds/seed.toml"
MANIFEST      := "manifest.json"
PREV_MANIFEST := "manifest.previous.json"
OUTPUT_DIR    := "src/adk_fluent"
TEST_DIR      := "tests/generated"
SCANNER       := "scripts/scanner.py"
SEED_GEN      := "scripts/seed_generator.py"
GENERATOR     := "scripts/generator.py"
A2UI_SPEC_DIR := "specification/v0_10/json"
A2UI_MANIFEST := "a2ui_manifest.json"
A2UI_SEED     := "seeds/a2ui_seed.toml"
A2UI_MANUAL   := "seeds/a2ui_seed.manual.toml"
IR_GEN        := "scripts/ir_generator.py"
DOC_GEN       := "scripts/doc_generator.py"
LLMS_GEN      := "scripts/llms_generator.py"
COOKBOOK_GEN   := "scripts/cookbook_generator.py"
SKILL_GEN     := "scripts/skill_generator.py"
DOC_DIR       := "docs/generated"
SPHINX_OUT    := "docs/_build/html"
COOKBOOK_DIR   := "examples/cookbook"

# Generated files — owned by `just generate`, not by formatters or pre-commit.
# This is the single source of truth; .pre-commit-config.yaml and .gitattributes mirror it.
GENERATED_PY  := "src/adk_fluent/agent.py src/adk_fluent/config.py src/adk_fluent/executor.py src/adk_fluent/planner.py src/adk_fluent/plugin.py src/adk_fluent/runtime.py src/adk_fluent/service.py src/adk_fluent/tool.py src/adk_fluent/workflow.py src/adk_fluent/_ir_generated.py"

# --- Full pipeline ---
all: scan seed generate a2ui docs skills docs-build
    @echo "\nPipeline complete. Generated code in {{OUTPUT_DIR}}/ and docs in {{DOC_DIR}}/"

# --- A2UI pipeline ---
a2ui: a2ui-scan a2ui-seed a2ui-generate
    @echo "\nA2UI pipeline complete."

a2ui-scan:
    @echo "Scanning A2UI spec..."
    @uv run python -m scripts.a2ui scan {{A2UI_SPEC_DIR}} -o {{A2UI_MANIFEST}}

a2ui-seed: _require-a2ui-manifest
    @echo "Generating A2UI seed..."
    @uv run python -m scripts.a2ui seed {{A2UI_MANIFEST}} -o {{A2UI_SEED}} --json --merge {{A2UI_MANUAL}}

a2ui-generate: _require-a2ui-seed
    @echo "Generating A2UI UI factories..."
    @uv run python -m scripts.a2ui generate {{A2UI_SEED}} --output-dir {{OUTPUT_DIR}} --test-dir {{TEST_DIR}}
    @uv run ruff check --fix {{OUTPUT_DIR}}/_ui_generated.py {{TEST_DIR}}/test_ui_generated.py || true
    @uv run ruff format {{OUTPUT_DIR}}/_ui_generated.py {{TEST_DIR}}/test_ui_generated.py

# --- Scan ADK ---
scan:
    @echo "Scanning installed google-adk..."
    @uv run python {{SCANNER}} -o {{MANIFEST}}
    @uv run python {{SCANNER}} --summary

# --- Generate seed.toml from manifest ---
seed: _require-manifest
    @echo "Generating seed.toml from manifest..."
    @uv run python {{SEED_GEN}} {{MANIFEST}} -o {{SEED}} --merge seeds/seed.manual.toml

# --- Generate code ---
# The generator owns its output files end-to-end: emit → ruff-format → write.
# ruff is integrated into the emitter (code_ir), so output is already canonical.
# The safety-net ruff check below should be a no-op — if it changes anything,
# the emitter's _ruff_format() is out of sync and should be investigated.
generate: _require-manifest _require-seed
    @echo "Generating code from seed + manifest..."
    @uv run python {{GENERATOR}} {{SEED}} {{MANIFEST}} \
        --output-dir {{OUTPUT_DIR}} \
        --test-dir {{TEST_DIR}}
    @uv run python {{IR_GEN}} {{MANIFEST}} --output {{OUTPUT_DIR}}/_ir_generated.py
    @# Safety net: verify generated output is already ruff-clean
    @uv run ruff check --fix {{GENERATED_PY}} {{TEST_DIR}} || true
    @uv run ruff format {{GENERATED_PY}} {{TEST_DIR}}
    @uv run ruff check {{GENERATED_PY}} {{TEST_DIR}}

# --- Stubs only (fast regeneration) ---
stubs: _require-manifest _require-seed
    @echo "Regenerating .pyi stubs only..."
    @uv run python {{GENERATOR}} {{SEED}} {{MANIFEST}} \
        --output-dir {{OUTPUT_DIR}} \
        --stubs-only

# --- Lint (hand-written files only — generated files are the generator's responsibility) ---
lint:
    @echo "Running lint checks (hand-written files)..."
    @uv run ruff check --exclude {{OUTPUT_DIR}}/agent.py --exclude {{OUTPUT_DIR}}/config.py --exclude {{OUTPUT_DIR}}/executor.py --exclude {{OUTPUT_DIR}}/planner.py --exclude {{OUTPUT_DIR}}/plugin.py --exclude {{OUTPUT_DIR}}/runtime.py --exclude {{OUTPUT_DIR}}/service.py --exclude {{OUTPUT_DIR}}/tool.py --exclude {{OUTPUT_DIR}}/workflow.py --exclude {{OUTPUT_DIR}}/_ir_generated.py --exclude {{TEST_DIR}} .
    @uv run ruff format --check --exclude {{OUTPUT_DIR}}/agent.py --exclude {{OUTPUT_DIR}}/config.py --exclude {{OUTPUT_DIR}}/executor.py --exclude {{OUTPUT_DIR}}/planner.py --exclude {{OUTPUT_DIR}}/plugin.py --exclude {{OUTPUT_DIR}}/runtime.py --exclude {{OUTPUT_DIR}}/service.py --exclude {{OUTPUT_DIR}}/tool.py --exclude {{OUTPUT_DIR}}/workflow.py --exclude {{OUTPUT_DIR}}/_ir_generated.py --exclude {{TEST_DIR}} .

# --- Format (hand-written files only — generated files are the generator's responsibility) ---
format:
    @echo "Formatting hand-written files..."
    @uv run ruff check --fix --exclude {{OUTPUT_DIR}}/agent.py --exclude {{OUTPUT_DIR}}/config.py --exclude {{OUTPUT_DIR}}/executor.py --exclude {{OUTPUT_DIR}}/planner.py --exclude {{OUTPUT_DIR}}/plugin.py --exclude {{OUTPUT_DIR}}/runtime.py --exclude {{OUTPUT_DIR}}/service.py --exclude {{OUTPUT_DIR}}/tool.py --exclude {{OUTPUT_DIR}}/workflow.py --exclude {{OUTPUT_DIR}}/_ir_generated.py --exclude {{TEST_DIR}} . || true
    @uv run ruff format --exclude {{OUTPUT_DIR}}/agent.py --exclude {{OUTPUT_DIR}}/config.py --exclude {{OUTPUT_DIR}}/executor.py --exclude {{OUTPUT_DIR}}/planner.py --exclude {{OUTPUT_DIR}}/plugin.py --exclude {{OUTPUT_DIR}}/runtime.py --exclude {{OUTPUT_DIR}}/service.py --exclude {{OUTPUT_DIR}}/tool.py --exclude {{OUTPUT_DIR}}/workflow.py --exclude {{OUTPUT_DIR}}/_ir_generated.py --exclude {{TEST_DIR}} .

# --- Format (alias) ---
alias fmt := format

# --- Format changed files only (fast, excludes generated files) ---
fmt-changed:
    #!/usr/bin/env bash
    set -euo pipefail
    # Generated files that formatters must never touch
    generated_re='(agent|config|executor|planner|plugin|runtime|service|tool|workflow)\.(py|pyi)$|_ir_generated\.py$|^tests/generated/'
    py_files=$(git diff --name-only --diff-filter=d HEAD -- '*.py' | grep -Ev "$generated_re" || true)
    if [ -n "$py_files" ]; then
        echo "Formatting $(echo "$py_files" | wc -w) Python files..."
        echo "$py_files" | xargs uv run ruff check --fix || true
        echo "$py_files" | xargs uv run ruff format
    else
        echo "No changed hand-written .py files to format."
    fi

# --- Verify generated files are up-to-date (idempotency gate) ---
check-gen: _require-manifest _require-seed
    #!/usr/bin/env bash
    set -euo pipefail
    echo "Verifying generated files are up-to-date..."
    # Stash any uncommitted hand-written changes
    just generate
    if git diff --quiet -- {{GENERATED_PY}} {{TEST_DIR}}; then
        echo "Generated files are up-to-date."
    else
        echo "ERROR: Generated files are stale. Run 'just generate' and commit the result."
        git diff --stat -- {{GENERATED_PY}} {{TEST_DIR}}
        exit 1
    fi

# --- Tests ---
test:
    @echo "Running tests..."
    @uv run pytest tests/ -v --tb=short

# --- Pipeline tests only (fast inner loop) ---
test-pipeline:
    @echo "Running pipeline tests (generator/seed_generator/code_ir)..."
    @uv run pytest tests/test_code_ir.py tests/test_seed_generator.py tests/test_generator_golden.py tests/test_property_based.py -v --tb=short

# --- Update golden files ---
update-golden:
    @echo "Updating golden files..."
    @uv run pytest tests/test_generator_golden.py --update-golden -v

# --- Preflight: run pre-commit hooks (mirrors CI lint exactly) ---
preflight:
    @echo "Running pre-commit hooks (same as CI lint)..."
    @uv run pre-commit run --all-files --show-diff-on-failure

# --- Local CI: full pipeline matching GitHub Actions ---
ci: preflight check-gen test
    @echo "\nLocal CI passed. Safe to push."

# --- Type checking ---
typecheck:
    @echo "Type-checking generated stubs..."
    @uv run pyright {{OUTPUT_DIR}}/*.pyi --pythonversion 3.12

# --- Type checking hand-written code ---
typecheck-core:
    @echo "Type-checking hand-written code..."
    @uv run pyright

# --- Watch mode ---
watch:
    @echo "Watching for changes..."
    @uv run watchfiles "just generate test typecheck" scripts/ seeds/ src/ tests/ manual/

# --- Documentation ---
docs: _require-manifest _require-seed
    @echo "Generating documentation..."
    @uv run python {{DOC_GEN}} {{SEED}} {{MANIFEST}} \
        --output-dir {{DOC_DIR}} \
        --cookbook-dir {{COOKBOOK_DIR}}
    @echo "Updating README.md from template..."
    @uv run python scripts/readme_generator.py
    @echo "Generating concepts documentation..."
    @uv run python scripts/concepts_generator.py
    @echo "Generating llms.txt and editor rules..."
    @uv run python {{LLMS_GEN}} {{MANIFEST}} {{SEED}}

# --- LLMs context files (editor rules, llms.txt) ---
llms: _require-manifest _require-seed
    @echo "Generating llms.txt and editor rules..."
    @uv run python {{LLMS_GEN}} {{MANIFEST}} {{SEED}}

# --- Agent Skills reference generation ---
skills: _require-manifest _require-seed
    @echo "Generating agent skill references..."
    @uv run python {{SKILL_GEN}} {{MANIFEST}} {{SEED}}

docs-api: _require-manifest _require-seed
    @echo "Generating API reference..."
    @uv run python {{DOC_GEN}} {{SEED}} {{MANIFEST}} \
        --output-dir {{DOC_DIR}} \
        --api-only

docs-cookbook: _require-manifest _require-seed
    @echo "Generating cookbook..."
    @uv run python {{DOC_GEN}} {{SEED}} {{MANIFEST}} \
        --output-dir {{DOC_DIR}} \
        --cookbook-dir {{COOKBOOK_DIR}} \
        --cookbook-only

docs-migration: _require-manifest _require-seed
    @echo "Generating migration guide..."
    @uv run python {{DOC_GEN}} {{SEED}} {{MANIFEST}} \
        --output-dir {{DOC_DIR}} \
        --migration-only

# --- Sphinx build ---
docs-build: docs
    @echo "Building Sphinx documentation..."
    @uv run sphinx-build -W --keep-going -b html docs/ {{SPHINX_OUT}}

# --- Sphinx live preview ---
docs-serve: docs
    @echo "Serving docs with live reload at http://localhost:8000..."
    @uv run sphinx-autobuild docs/ {{SPHINX_OUT}} --watch {{DOC_DIR}} --watch src/ --port 8000

# --- REPL ---
repl:
    @echo "Starting ADK-Fluent playground..."
    @uv run ipython -i -c "from adk_fluent import *; print('\nADK-Fluent playground loaded! (Agent, Pipeline, S, C available)')"

# --- Worktree: isolated workspace for feature work ---
worktree name:
    #!/usr/bin/env bash
    set -euo pipefail
    dir=".worktrees/{{name}}"
    branch="feature/{{name}}"
    if [ -d "$dir" ]; then
        echo "Worktree already exists at $dir"
        echo "cd $dir"
        exit 0
    fi
    echo "Creating worktree at $dir (branch: $branch)..."
    git worktree add "$dir" -b "$branch"
    echo "Installing dependencies..."
    cd "$dir" && uv sync --all-extras
    echo ""
    echo "Worktree ready: cd $dir"
    echo "When done: git worktree remove $dir"

# --- Add Cookbook ---
add-cookbook name:
    @uv run python scripts/add_cookbook.py "{{name}}"

# --- Cookbook generation ---
cookbook-gen: _require-manifest _require-seed
    @echo "Generating cookbook example stubs..."
    @uv run python {{COOKBOOK_GEN}} {{SEED}} {{MANIFEST}} \
        --cookbook-dir {{COOKBOOK_DIR}}

cookbook-gen-dry: _require-manifest _require-seed
    @echo "Previewing cookbook example stubs..."
    @uv run python {{COOKBOOK_GEN}} {{SEED}} {{MANIFEST}} \
        --cookbook-dir {{COOKBOOK_DIR}} --dry-run

# --- Convert cookbook to adk-web agent folders ---
agents:
    @echo "Converting cookbook examples to adk-web agent folders..."
    @uv run python scripts/cookbook_to_agents.py --force

# --- Visual: A2UI preview (static, no LLM, no server) ---
a2ui-preview:
    @echo "Exporting A2UI surfaces from cookbooks..."
    @uv run python scripts/export_a2ui_surfaces.py
    @echo "Opening A2UI gallery in browser..."
    @python3 -c "import webbrowser; webbrowser.open('visual/index.html')" 2>/dev/null || echo "Open visual/index.html in your browser"

# --- Visual: full cookbook runner (requires API key) ---
visual: a2ui-preview
    @echo "Starting visual cookbook runner at http://localhost:8099..."
    @uv run uvicorn visual.server:app --host 0.0.0.0 --port 8099 --reload

# --- Visual: export surfaces only ---
visual-export:
    @echo "Exporting A2UI surfaces..."
    @uv run python scripts/export_a2ui_surfaces.py

# --- Visual test suite (requires API key) ---
test-visual:
    @echo "Running visual test suite..."
    @uv run pytest tests/visual/ -v --tb=short -m visual

# --- Diff against previous ---
diff:
    #!/usr/bin/env bash
    if [ -f {{PREV_MANIFEST}} ]; then
        echo "Changes since last scan:"
        uv run python {{SCANNER}} --diff {{PREV_MANIFEST}}
    else
        echo "No previous manifest found. Run 'just scan' first."
    fi

# --- Diff as publishable Markdown page ---
diff-md:
    #!/usr/bin/env bash
    if [ -f {{PREV_MANIFEST}} ]; then
        echo "Generating API diff Markdown..."
        uv run python {{SCANNER}} --diff {{PREV_MANIFEST}} \
            --diff-markdown {{DOC_DIR}}/api-diff.md
        echo "Written to {{DOC_DIR}}/api-diff.md"
    else
        echo "No previous manifest found. Run 'just archive' before scanning, then 'just diff-md'."
    fi

# --- Summary ---
summary:
    @uv run python {{SCANNER}} --summary

# --- Archive current manifest ---
archive:
    @cp {{MANIFEST}} {{PREV_MANIFEST}}
    @echo "Archived {{MANIFEST}} -> {{PREV_MANIFEST}}"

# --- Package build ---
build: all
    @echo "Building package..."
    @uv build

# --- Publish to TestPyPI ---
publish-test: build
    @echo "Publishing to TestPyPI..."
    @uv publish --index testpypi

# --- Publish to PyPI ---
publish: build
    @echo "Publishing to PyPI..."
    @uv publish

# --- Clean ---
clean:
    @echo "Cleaning generated files..."
    @rm -rf {{OUTPUT_DIR}}/*.py {{OUTPUT_DIR}}/*.pyi
    @rm -rf {{TEST_DIR}}/
    @rm -rf {{DOC_DIR}}/
    @rm -rf docs/_build/
    @rm -f {{MANIFEST}}
    @echo "Done."

# --- Help ---
help:
    @echo "ADK-FLUENT Development Commands:"
    @echo ""
    @echo "  just setup          First-time setup: install deps + pre-commit hooks"
    @echo "  just all            Full pipeline: scan -> seed -> generate -> docs"
    @echo "  just scan           Introspect ADK -> manifest.json"
    @echo "  just seed           manifest.json -> seed.toml"
    @echo "  just generate       seed.toml + manifest.json -> code (self-formatting)"
    @echo "  just stubs          Regenerate .pyi stubs only"
    @echo "  just lint           Lint hand-written files (ruff check + format check)"
    @echo "  just fmt            Auto-format hand-written files (ruff)"
    @echo "  just fmt-changed    Auto-format only changed hand-written files (fast)"
    @echo "  just check-gen      Verify generated files are up-to-date"
    @echo "  just preflight      Run pre-commit hooks (mirrors CI lint)"
    @echo "  just ci             Full local CI: preflight + check-gen + test"
    @echo "  just test           Run pytest suite"
    @echo "  just test-pipeline  Run pipeline tests only (fast <5s)"
    @echo "  just update-golden  Regenerate golden files"
    @echo "  just typecheck      Run pyright type-check"
    @echo "  just watch          Auto-run generate+test on changes"
    @echo "  just repl           Pre-loaded IPython playground"
    @echo "  just worktree NAME  Create isolated worktree + install deps"
    @echo "  just add-cookbook    Scaffold new example"
    @echo "  just docs           Generate all documentation"
    @echo "  just docs-api       Generate API reference only"
    @echo "  just docs-cookbook   Generate cookbook only"
    @echo "  just docs-migration Generate migration guide only"
    @echo "  just docs-build     Build Sphinx HTML documentation"
    @echo "  just docs-serve     Build and serve docs with live reload"
    @echo "  just llms           Generate llms.txt + editor rules (CLAUDE.md, .cursorrules, etc.)"
    @echo "  just skills         Generate agent skill references (.claude/skills/ + .gemini/skills/ + skills/)"
    @echo "  just cookbook-gen    Generate cookbook example stubs"
    @echo "  just cookbook-gen-dry Preview cookbook stubs (dry-run)"
    @echo "  just agents         Convert cookbook -> adk web folders"
    @echo "  just a2ui-preview   Static A2UI gallery (no server, no LLM)"
    @echo "  just visual         Full visual runner (requires API key)"
    @echo "  just visual-export  Export A2UI surfaces to JSON"
    @echo "  just test-visual    Run visual regression tests"
    @echo "  just diff           Show changes since last scan (JSON)"
    @echo "  just diff-md        Generate API diff as docs/generated/api-diff.md"
    @echo "  just build          Build pip package"
    @echo "  just publish        Publish to PyPI"
    @echo "  just clean          Remove generated files"
    @echo ""
    @echo "Workflow: just setup -> just all -> just ci -> commit"
    @echo ""
    @echo "Architecture: Generated files are owned by 'just generate'."
    @echo "              Formatters and pre-commit only touch hand-written files."
    @echo "              See .gitattributes for the generated file list."

# --- Internal: prerequisite checks ---
[private]
_require-manifest:
    @test -f {{MANIFEST}} || (echo "ERROR: {{MANIFEST}} not found. Run 'just scan' first." && exit 1)

[private]
_require-seed:
    @test -f {{SEED}} || (echo "ERROR: {{SEED}} not found. Run 'just seed' first." && exit 1)

[private]
_require-a2ui-manifest:
    @test -f {{A2UI_MANIFEST}} || (echo "ERROR: {{A2UI_MANIFEST}} not found. Run 'just a2ui-scan' first." && exit 1)

[private]
_require-a2ui-seed:
    @test -f {{A2UI_SEED}} || (echo "ERROR: {{A2UI_SEED}} not found. Run 'just a2ui-seed' first." && exit 1)
