# Set shell for commands
set shell := ["bash", "-c"]

project_root := justfile_directory()

# Load environment variables from .env file if it exists
set dotenv-load := true

[doc("Show available recipes")]
default:
    @just --list

# =============================================================================
# LAMINA LAYER BUILDS
# =============================================================================

lamina_root      := justfile_directory() + "/../lamina"
libre_forge_root := justfile_directory() + "/../workspaces/libre-forge"
lian_build_root  := justfile_directory() + "/../lian-build"
secrets_base     := justfile_directory() + "/../workspaces/libre-daoshi"
rustelo_root     := justfile_directory() + "/{{RUSTELO_ROOT}}"
strops_root      := justfile_directory() + "/../stratumiops"

# Build and push: no arg or "website" → build this project via fleet; layer name → delegate to lamina
# Pass log=/path/file.log to capture output to a file instead of printing to screen.
build-push-remote layer="website" log="":
    #!/usr/bin/env nu
    if "{{ layer }}" != "website" {
        just -f "{{ lamina_root }}/justfile" build-push-remote {{ layer }}
        return
    }
    let forge    = "{{ libre_forge_root }}"
    let kage     = ($forge | path join "infra/control-plane/.kage")
    let sc_file  = ($forge | path join "infra/control-plane/secrets/sccache.sops.yaml")
    let nk_file  = ($forge | path join "infra/control-plane/secrets/nkeys.sops.yaml")
    let handler  = ($forge | path join "scripts/provision-relay.nu")
    for p in [$kage, $sc_file, $nk_file] {
        if not ($p | path exists) { error make { msg: $"missing: ($p)" } }
    }
    let sc_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
        do { ^sops --decrypt --output-type json $sc_file } | complete
    })
    if $sc_raw.exit_code != 0 { error make { msg: $"sops sccache: ($sc_raw.stderr | str trim)" } }
    let sc = ($sc_raw.stdout | from json)
    let nk_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
        do { ^sops --decrypt --extract '["forge_control_seed"]' $nk_file } | complete
    })
    if $nk_raw.exit_code != 0 { error make { msg: $"sops nkeys: ($nk_raw.stderr | str trim)" } }
    let nk_seed = ($nk_raw.stdout | str trim)
    let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)
    let log_file = if ("{{ log }}" != "") { "{{ log }}" } else { null }
    if $log_file != null { print $"  lian-build log:  ($log_file)" }
    let ctx = (^mktemp -d | str trim)
    print $"  assembling context: ($ctx)"
    ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "--exclude=node_modules/" "--exclude=.coder/" "{{ justfile_directory() }}/" $"($ctx)/"
    ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ rustelo_root }}/" $"($ctx)/rustelo/"
    if ("{{ strops_root }}" | path exists) {
        ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ strops_root }}/" $"($ctx)/stratumiops/"
    }
    # Write version-patched project.ncl into lian_build_root so Nickel finds it by logical name.
    # build_directives.ncl does `import "jpl-project.ncl"` — resolved via LIAN_BUILD_NICKEL_IMPORT_PATH
    # which already points to lian_build_root. No path changes needed; file is deleted on exit.
    let proj_override = $"{{ lian_build_root }}/jpl-project.ncl"
    open "{{ justfile_directory() }}/provisioning/project.ncl"
        | str replace 'image_tag      = "latest"' $'image_tag      = "($image_tag)"'
        | save -f $proj_override

    let dir_raw = (do {
        ^nickel export --import-path "{{ lian_build_root }}" "{{ justfile_directory() }}/lian-build/build_directives.ncl"
    } | complete)
    if $dir_raw.exit_code != 0 { rm -f $proj_override; error make { msg: $"nickel export directives: ($dir_raw.stderr | str trim)" } }
    let fleet_nats_url = ($dir_raw.stdout | from json | get adapter.nats_url)

    # nats CLI requires the NKey seed as a file (--nkey), not as $NATS_SEED string.
    let nkey_file = (^mktemp | str trim)
    $nk_seed | save -f $nkey_file

    let relay_job = (job spawn --description "provision-relay" {
        ^nats --server $fleet_nats_url --nkey $nkey_file reply "fleet.libre-forge.ops.provision.>" --command $"nu ($handler)"
    })
    print $"  provision-relay: job ($relay_job)"
    print $"  daemon logs:     ssh libre-daoshi-0 'k0s kubectl -n fleet-system logs -l app=fleet-daemon -f'"
    let daoshi_kage = ("{{ secrets_base }}" | path join "infra/libre-daoshi/.kage")
    try {
        with-env {
            AWS_ACCESS_KEY_ID: $sc.access_key_id,
            AWS_SECRET_ACCESS_KEY: $sc.secret_access_key,
            NATS_NKEY_SEED: $nk_seed,
            LIAN_BUILD_NICKEL_IMPORT_PATH: "{{ lian_build_root }}",
            SOPS_AGE_KEY_FILE: $daoshi_kage,
        } {
            if $log_file != null {
                ^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives.ncl" --context $ctx --secrets-base "{{ secrets_base }}" o+e> $log_file
            } else {
                ^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives.ncl" --context $ctx --secrets-base "{{ secrets_base }}"
            }
        }
    } catch { |e|
        job kill $relay_job
        rm -f $nkey_file $proj_override
        rm -rf $ctx
        let hint = if $log_file != null { $" — see ($log_file)" } else { "" }
        error make { msg: $"lian-build failed($hint)\n($e.msg)" }
    }
    job kill $relay_job
    rm -f $nkey_file $proj_override
    rm -rf $ctx

# Build server binary only: WASM is compiled locally then sent as part of the context.
# wasm=<path>  override the WASM artifacts dir (default: target/site/).
#              If absent and no override is given, builds WASM locally first.
# log=<path>   capture lian-build output to a file instead of printing to screen.
build-push-server wasm="" log="":
    #!/usr/bin/env nu
    let forge    = "{{ libre_forge_root }}"
    let kage     = ($forge | path join "infra/control-plane/.kage")
    let sc_file  = ($forge | path join "infra/control-plane/secrets/sccache.sops.yaml")
    let nk_file  = ($forge | path join "infra/control-plane/secrets/nkeys.sops.yaml")
    let handler  = ($forge | path join "scripts/provision-relay.nu")
    for p in [$kage, $sc_file, $nk_file] {
        if not ($p | path exists) { error make { msg: $"missing: ($p)" } }
    }
    let sc_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
        do { ^sops --decrypt --output-type json $sc_file } | complete
    })
    if $sc_raw.exit_code != 0 { error make { msg: $"sops sccache: ($sc_raw.stderr | str trim)" } }
    let sc = ($sc_raw.stdout | from json)
    let nk_raw = (with-env { SOPS_AGE_KEY_FILE: $kage } {
        do { ^sops --decrypt --extract '["forge_control_seed"]' $nk_file } | complete
    })
    if $nk_raw.exit_code != 0 { error make { msg: $"sops nkeys: ($nk_raw.stderr | str trim)" } }
    let nk_seed = ($nk_raw.stdout | str trim)
    let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)

    let wasm_site = if ("{{ wasm }}" != "") {
        "{{ wasm }}"
    } else {
        let target_dir = (
            $env.CARGO_TARGET_DIR? | default (
                let cfg = "{{ justfile_directory() }}/.cargo/config.toml"
                if ($cfg | path exists) {
                    (open $cfg).build?."target-dir"? | default "{{ justfile_directory() }}/target"
                } else {
                    "{{ justfile_directory() }}/target"
                }
            )
        )
        $target_dir | path join "site"
    }
    if not ($wasm_site | path exists) {
        print "  building WASM frontend locally (no artifacts at ($wasm_site))..."
        with-env {
            SITE_CONFIG_PATH: "{{ justfile_directory() }}/site/config/index.ncl",
            NICKEL_IMPORT_PATH: $"{{ rustelo_root }}/resources/nickel:{{ justfile_directory() }}/site/config",
        } {
            ^cargo leptos build --frontend-only --release
        }
    }

    let log_file = if ("{{ log }}" != "") { "{{ log }}" } else { null }
    if $log_file != null { print $"  lian-build log:  ($log_file)" }
    let ctx = (^mktemp -d | str trim)
    print $"  assembling context: ($ctx)"
    ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "--exclude=node_modules/" "--exclude=.coder/" "{{ justfile_directory() }}/" $"($ctx)/"
    ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ rustelo_root }}/" $"($ctx)/rustelo/"
    if ("{{ strops_root }}" | path exists) {
        ^rsync -a --delete "--exclude=.git/" "--exclude=target/" "{{ strops_root }}/" $"($ctx)/stratumiops/"
    }
    # Inject WASM artifacts into context as wasm-site/
    ^rsync -a --delete $"($wasm_site)/" $"($ctx)/wasm-site/"
    print $"  wasm-site:         ($wasm_site)"

    # Write version-patched project.ncl into lian_build_root — same approach as build-push-remote.
    let proj_override = $"{{ lian_build_root }}/jpl-project.ncl"
    open "{{ justfile_directory() }}/provisioning/project.ncl"
        | str replace 'image_tag      = "latest"' $'image_tag      = "($image_tag)"'
        | save -f $proj_override

    let dir_raw = (do {
        ^nickel export --import-path "{{ lian_build_root }}" "{{ justfile_directory() }}/lian-build/build_directives_server.ncl"
    } | complete)
    if $dir_raw.exit_code != 0 { rm -f $proj_override; error make { msg: $"nickel export directives: ($dir_raw.stderr | str trim)" } }
    let fleet_nats_url = ($dir_raw.stdout | from json | get adapter.nats_url)

    let nkey_file = (^mktemp | str trim)
    $nk_seed | save -f $nkey_file

    let relay_job = (job spawn --description "provision-relay" {
        ^nats --server $fleet_nats_url --nkey $nkey_file reply "fleet.libre-forge.ops.provision.>" --command $"nu ($handler)"
    })
    print $"  provision-relay: job ($relay_job)"
    print $"  daemon logs:     ssh libre-daoshi-0 'k0s kubectl -n fleet-system logs -l app=fleet-daemon -f'"
    let daoshi_kage = ("{{ secrets_base }}" | path join "infra/libre-daoshi/.kage")
    try {
        with-env {
            AWS_ACCESS_KEY_ID: $sc.access_key_id,
            AWS_SECRET_ACCESS_KEY: $sc.secret_access_key,
            NATS_NKEY_SEED: $nk_seed,
            LIAN_BUILD_NICKEL_IMPORT_PATH: "{{ lian_build_root }}",
            SOPS_AGE_KEY_FILE: $daoshi_kage,
        } {
            if $log_file != null {
                ^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives_server.ncl" --context $ctx --secrets-base "{{ secrets_base }}" o+e> $log_file
            } else {
                ^lian-build build --directives "{{ justfile_directory() }}/lian-build/build_directives_server.ncl" --context $ctx --secrets-base "{{ secrets_base }}"
            }
        }
    } catch { |e|
        job kill $relay_job
        rm -f $nkey_file $proj_override
        rm -rf $ctx
        let hint = if $log_file != null { $" — see ($log_file)" } else { "" }
        error make { msg: $"lian-build failed($hint)\n($e.msg)" }
    }
    job kill $relay_job
    rm -f $nkey_file $proj_override
    rm -rf $ctx

libre_wuji_root := justfile_directory() + "/../workspaces/secrets-base"

# Register this project into a workspace repo, injecting the current Cargo.toml version as image_tag.
# workspace=<path>  override the target workspace root (default: secrets-base sibling).
[doc("Write versioned appserv/builds stubs into the workspace repo")]
register workspace="" workspace_name="secrets-base":
    #!/usr/bin/env nu
    let image_tag = (open "{{ justfile_directory() }}/Cargo.toml" | get workspace.package.version)
    let ws = if ("{{ workspace }}" | is-not-empty) { "{{ workspace }}" } else { "{{ libre_wuji_root }}" }
    if not ($ws | path exists) { error make { msg: $"workspace not found: ($ws)" } }
    print $"[register] image_tag: ($image_tag), workspace: ($ws)"
    ^nu "{{ justfile_directory() }}/provisioning/register.nu" --workspace $ws --workspace-name "{{ workspace_name }}" --image-tag $image_tag

# =============================================================================
# MODULE IMPORTS
# =============================================================================

mod dev     "justfiles/dev.just"
mod build   "justfiles/build.just"
mod database "justfiles/database.just"
mod docs    "justfiles/docs.just"
mod test    "justfiles/test.just"
mod utils   "justfiles/utils.just"
mod tools   "justfiles/tools.just"
mod helptext "justfiles/helptext.just"
mod cache   "justfiles/cache.just"
mod ui      "justfiles/ui.just"
mod content "justfiles/content.just"

# =============================================================================
# ALIASES
# =============================================================================

alias a  := list-alias
alias b  := build-dev
alias bp := build-prod
alias s  := dev-full
alias sp := serve-prod
alias sc := serve-cms
alias cb := content-build
alias oa := dev::organize-artifacts
alias sbl := server-browser-logs
alias bl  := browser-logs
alias t  := test-run
alias d  := start
alias df := dev-fast
alias dc := dev-coordinated
alias dm := dev-minimal
alias ba := build-artifacts
alias bs := build-status
alias ca := clean-build-artifacts

alias cs := cache::cs
alias cc := cache::cache-clean
alias cca := cache::cache-clean
# alias dev := start  # Conflicts with module name
# alias h  := help  # Defined as recipe below
alias ha := help-all
alias o  := overview
alias c  := cross-build
alias pt := page-tester
alias pr := pages-report
alias pn := page-new
alias sh := show
# Tools aliases
alias ta := build-tools-analyze
alias tm := build-tools-manage
alias tg := build-tools-generate-page
alias ts := build-tools-status

# Navigation Testing aliases
alias nts := dev::with-nav-test           # Nav Test Start
alias nt := dev::nav-test-route           # Nav Test route
alias ns := dev::nav-test-sequence        # Nav test Sequence
alias nv := dev::nav-test-validate        # Nav Validate
alias nb := dev::nav-test-bench           # Nav Bench
alias na := dev::nav-test-all             # Nav All tests
alias nc := dev::nav-quick-check          # Nav Check (CI/CD)
alias nd := dev::nav-dashboard            # Nav Dashboard
alias nh := dev::nav-help                 # Nav Help

# Quick nav test aliases for common routes
alias nt-home := nav-test-home            # Test home route
alias nt-services := nav-test-services    # Test services route
alias nt-contact := nav-test-contact      # Test contact route
alias nt-blog := nav-test-blog            # Test blog route
alias nt-es := nav-test-es                # Test Spanish home
alias nt-contactar := nav-test-contactar  # Test Spanish contact
# UI Management aliases
alias routes := ui::routes-list
alias pages := ui::pages-list
alias components := ui::components-list

# =============================================================================
# CORE COMMANDS (Backward Compatibility & Common Tasks)
# =============================================================================

list-alias:
    @echo "🔗 List of aliases:"
    @cat {{justfile()}} | grep "^alias" | sed 's/^alias /   /g'

# Show comprehensive system overview
overview:
    @just utils::overview

# Show information about specified topic (try: info, status, config, overview, aliases, modules, help)
show *TOPIC:
    #!/usr/bin/env bash
    TOPIC="{{TOPIC}}"
    if [ -z "$TOPIC" ]; then
        echo "📋 Available topics to show:"
        echo "  info        - Project information"
        echo "  status      - Application health status"
        echo "  config      - Configuration settings"
        echo "  overview    - System overview"
        echo "  aliases     - List of command aliases"
        echo "  modules     - Available modules"
        echo "  build-tools - Build tools system"
        echo "  help        - Help system"
        echo ""
        echo "Usage: just show <topic> or just sh <topic>"
        exit 0
    fi
    case "$TOPIC" in
        "info"|"information") just utils::info ;;
        "status"|"health") just utils::health ;;
        "config"|"configuration") just utils::config ;;
        "overview"|"system") just utils::overview ;;
        "aliases"|"alias") just list-alias ;;
        "modules"|"module") just helptext::modules ;;
        "build-tools"|"tools"|"tool") just helptext::tools ;;
        "help") just help ;;
        *)
            echo "📋 Available topics to show:"
            echo "  info        - Project information"
            echo "  status      - Application health status"
            echo "  config      - Configuration settings"
            echo "  overview    - System overview"
            echo "  aliases     - List of command aliases"
            echo "  modules     - Available modules"
            echo "  build-tools - Build tools system"
            echo "  help        - Help system"
            echo ""
            echo "Usage: just show <topic> or just sh <topic>"
            ;;
    esac

# =============================================================================
# PRIMARY WORKFLOW COMMANDS (Unified Interface)
# =============================================================================

# Start development server (main entry point)
start:
    @just dev::serve

# Backward compatibility: redirect 'dev' to 'start'
dev-start:
    @echo "💡 Note: 'just dev' has been renamed to 'just start'"
    @echo "🔄 Redirecting to development server..."
    @just start

# Start development server with full features
dev-full:
    @just dev::full

# Build for development
build-dev:
    @just build::dev

# Build for production
build-prod:
    @just build::prod

# Smoke-test the htmx-ssr surface after a build (full page / fragment / theme / lang)
htmx-smoke base="http://127.0.0.1:3030":
    @set -e; BASE="{{base}}"; \
     echo "→ full page must include <!DOCTYPE> and /assets/htmx/htmx.min.js"; \
     curl -fsS "$BASE/" | grep -q "<!DOCTYPE" || (echo "FAIL: missing DOCTYPE"; exit 1); \
     echo "→ fragment must omit <!DOCTYPE> and <html>"; \
     resp=$(curl -fsS -H "HX-Request: true" "$BASE/"); \
     echo "$resp" | grep -qv "<!DOCTYPE" || (echo "FAIL: fragment contains DOCTYPE"; exit 1); \
     echo "→ theme toggle returns 200 and Set-Cookie: theme=…"; \
     curl -fsSI -X POST -H "Cookie: theme=light" "$BASE/api/htmx/theme/toggle" \
       | grep -i "^set-cookie: theme=" || (echo "FAIL: theme toggle Set-Cookie missing"; exit 1); \
     echo "→ unknown lang returns 400 with HX-Retarget"; \
     curl -fsSI -X POST -H "HX-Request: true" "$BASE/api/htmx/lang/zz" -o /dev/null -w "%{http_code} %{header_HX-Retarget}\n" \
       | grep -q "400" || (echo "FAIL: bad lang did not 400"; exit 1); \
     echo "✅ htmx-ssr smoke checks passed"

# Switch rendering profile and rebuild. Without args: shows current state and prompts.
switch-rendering profile="":
    #!/usr/bin/env nu
    let ncl = "site/config/rendering.ncl"
    let profiles = [
        { name: "leptos-hydration", label: "Leptos hydration  — SSR + WASM bundle, full reactive client" },
        { name: "htmx-ssr",         label: "htmx-ssr          — SSR only, no WASM, htmx interactivity"   },
    ]

    # Read current profile from rendering.ncl
    let current = (
        open $ncl
        | parse --regex 'default_profile = "(?P<p>[^"]+)"'
        | get p?.0?
        | default "unknown"
    )

    let chosen = if ("{{ profile }}" == "") {
        print ""
        print $"  Current rendering profile: (ansi yellow_bold)($current)(ansi reset)"
        print ""
        for i in ($profiles | enumerate) {
            let marker = if $i.item.name == $current { $"(ansi green)▶(ansi reset)" } else { " " }
            print $"  ($marker) [($i.index + 1)] ($i.item.label)"
        }
        print ""
        let raw = (input "  Select [1/2] or Enter to cancel: " | str trim)
        if ($raw == "") { print "  Cancelled."; exit 0 }
        let idx = ($raw | into int | $in - 1)
        if $idx < 0 or $idx >= ($profiles | length) {
            error make { msg: $"Invalid selection: ($raw)" }
        }
        $profiles | get $idx | get name
    } else {
        "{{ profile }}"
    }

    let valid_names = ($profiles | get name)
    if ($chosen not-in $valid_names) {
        error make { msg: $"Unknown profile '($chosen)'. Valid: ($valid_names | str join ', ')" }
    }

    if $chosen == $current {
        print $"  Already on ($chosen) — nothing to do."
        exit 0
    }

    open $ncl
        | str replace --regex 'default_profile = "(leptos-hydration|htmx-ssr)"' $'default_profile = "($chosen)"'
        | save -f $ncl
    print $"  ✅ ($current) → ($chosen)"
    print ""
    if $chosen == "htmx-ssr" {
        print $"  Dev:  (ansi cyan)just dev::htmx(ansi reset)       — cargo watch, no wasm32"
        print $"  Prod: (ansi cyan)just build-auto(ansi reset)      — server-only binary"
    } else {
        print $"  Dev:  (ansi cyan)just start(ansi reset)           — cargo leptos serve"
        print $"  Prod: (ansi cyan)just build-auto(ansi reset)      — cargo leptos build --release"
    }

# Build for production with automatic WASM-or-native dispatch driven by site/config/rendering.ncl (ADR-006)
build-auto:
    @nu /Users/Akasha/Development/rustelo/scripts/check-wasm-needed.nu --verbose \
        --routes-dir site/config \
        --workspace-config site/config/rendering.ncl \
        && (echo "🔨 WASM required — cargo leptos build --release"; cargo leptos build --release) \
        || (echo "🪶 Pure htmx-ssr — skipping wasm32"; \
            cargo build --release -p rustelo-htmx-server --no-default-features --features htmx-ssr)

# Serve production build
serve-prod:
    @just build::serve-prod

# Serve with CMS features
serve-cms:
    @just build::serve-cms

# Run all tests
test-run:
    @just test::run

# Quality checks with mandatory rule validation (main interface)
quality:
    @echo "🔥 Running quality checks with MANDATORY rule validation..."
    @echo "📖 Reading critical rules from @rules/CLAUDE_CRITICAL_RULES.md..."
    @scripts/rules/validate-rules.nu --strict
    @just test::quality

# Build CSS files
css-build:
    @just dev::css-build

# Watch CSS files
css-watch:
    @just dev::css-watch

# Database setup
db-setup:
    @just database::setup

# Database migrations
db-migrate:
    @just database::migrate

# Generate documentation
docs-generate:
    @just docs::generate

# Build documentation
docs-build:
    @just docs::build

# Check code quality
check:
    @just test::check

# Format code
fmt:
    @just test::format

# Fast development modes
dev-fast:
    @just dev::dev-fast

dev-coordinated:
    @just dev::dev-coordinated

dev-minimal:
    @just dev::ultra-minimal

build-artifacts:
    @just dev::build-artifacts

build-status:
    @just dev::build-status

clean-build-artifacts:
    @just dev::clean-build-artifacts

# Complete project setup
setup:
    @just utils::setup

# Project information
info:
    @just utils::info

# Health check
health:
    @just utils::health

# =============================================================================
# NAVIGATION TESTING SHORTCUTS (Common Routes)
# =============================================================================

# Test home route
nav-test-home:
    @just dev::nav-test-route /

# Test services route
nav-test-services:
    @just dev::nav-test-route /services

# Test contact route
nav-test-contact:
    @just dev::nav-test-route /contact

# Test blog route
nav-test-blog:
    @just dev::nav-test-route /blog

# Test Spanish home
nav-test-es:
    @just dev::nav-test-route /es

# Test Spanish contact
nav-test-contactar:
    @just dev::nav-test-route /es/contactar

# Clean build artifacts
clean:
    @just build::clean

# =============================================================================
# CONVENIENCE COMMANDS (Direct Module Access)
# =============================================================================

# Browser testing
page-tester *ARGS:
    @just test::page {{ARGS}}

pages-report *ARGS:
    @just test::pages-report {{ARGS}}

page-new:
    @just test::page-new

# Cross-platform build
cross-build:
    @just build::cross

# Content operations
content-build:
    @just dev::content-build

# Export NCL metadata to index.json (NCL pipeline)
content-ncl-to-json type="" lang="":
    @just dev::content-ncl-to-json {{type}} {{lang}}

# Full NCL pipeline: _index.ncl → index.json + copy images
content-ncl-build type="" lang="":
    @just dev::content-ncl-build {{type}} {{lang}}

# Copy page-bundle images to site/public for HTTP serving
content-copy-images type="" lang="":
    @just dev::content-copy-images {{type}} {{lang}}

# Sync FTL files from source project index.html files (requires data-key attributes).
# Each project index.ncl must have source_html set.
sync-pages:
    nu scripts/sync/sync-all-pages.nu

# Sync a single project FTL. Usage: just sync-page ontoref
sync-page project:
    nu scripts/sync/sync-all-pages.nu --only {{project}}

# Preview FTL sync without writing. Usage: just sync-page-dry ontoref
sync-page-dry project:
    nu scripts/sync/sync-all-pages.nu --only {{project}} --dry-run


# Sync site/public → target/site (mirrors what cargo-leptos does at build time).
# Use when the dev server is not running and static assets need to be available immediately.
sync-public:
    @rsync -a --exclude='.DS_Store' site/public/ target/site/
    @echo "synced site/public → target/site"

content-generate type title *args:
    @just dev::content-generate {{type}} {{title}} {{args}}

content-generate-indices:
    @just dev::content-generate-indices

# Generate *.ncl metadata files from existing markdown frontmatter
content-md-to-ncl type="" lang="":
    @nu scripts/content/md-to-ncl.nu \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }}

# Fill translations field in all *.ncl files by cross-lang stem matching (implies --force)
content-md-to-ncl-translations type="" lang="":
    @nu scripts/content/md-to-ncl.nu --fill-translations \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }}

# Validate all generated post *.ncl files pass their Nickel contracts
content-ncl-validate type="" lang="":
    @nu scripts/content/md-to-ncl.nu --validate-contracts \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }}

# Generate _index.ncl files from per-post *.ncl metadata files
# Generate _index.ncl files. Pass validate=true to also check slug/id uniqueness.
# Usage: just content-ncl-index [type] [lang] [validate]
content-ncl-index type="" lang="" validate="":
    @nu scripts/content/generate-ncl-index.nu \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }} \
        {{ if validate != "" { "--validate" } else { "" } }}

# Generate + validate slug/id uniqueness in one step
content-ncl-index-validate type="" lang="":
    @nu scripts/content/generate-ncl-index.nu --validate \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }}

# Same as content-ncl-index but shows what would be written without writing
content-ncl-index-dry type="" lang="":
    @nu scripts/content/generate-ncl-index.nu --dry-run --verbose \
        --content-dir "${SITE_CONTENT_PATH:-site/content}" \
        {{ if type != "" { "--type " + type } else { "" } }} \
        {{ if lang != "" { "--lang " + lang } else { "" } }}

# Standalone build-tools generation command
build-tools-info:
    @just dev::build-tools-info

# Tools operations
build-tools-analyze format="markdown" output="site/info":
    @just tools::build-tools-analyze {{format}} {{output}}

build-tools-manage mode="dashboard":
    @just tools::tools-manage {{mode}}

build-tools-generate-page name template="basic":
    @just tools::build-tools-generate-page {{name}} {{template}}

build-tools-status:
    @just tools::build-tools-status

# Install development dependencies
npm-install:
    @just utils::npm-install

cargo-check:
    @just utils::cargo-check

# =============================================================================
# WORKFLOW COMMANDS
# =============================================================================

# Complete development workflow
workflow-dev:
    @echo "🔄 Running development workflow..."
    @just utils::setup-deps
    @just css-build
    @just check
    @just test
    @just dev

# Complete production workflow
workflow-prod:
    @echo "🔄 Running production workflow..."
    @just test::quality
    @just build-prod
    @just build::docker
    @just deploy

# Pre-commit workflow
pre-commit:
    @echo "🔄 Running pre-commit workflow..."
    @just fmt
    @just test::check-strict
    @just test
    @just css-build

# CI/CD workflow
ci:
    @echo "🔄 Running CI/CD workflow..."
    @just test::format-check
    @just test::check-strict
    @just test
    @just test::audit
    @just build-prod

# =============================================================================
# HELP COMMANDS (Delegated to help module)
# =============================================================================

# Show help for development commands
help-dev:
    @just helptext::dev

# Show help for build commands
help-build:
    @just helptext::build

# Show help for testing commands
help-test:
    @just helptext::test

# Show help for database commands
help-db:
    @just helptext::db

# Show help for documentation commands
help-docs:
    @just helptext::docs

# Show help for utility commands
help-utils:
    @just helptext::utils

# Show help for build-tools commands
help-build-tools:
    @just helptext::build-tools

# Show help for modules
help-modules:
    @just helptext::modules

# Show comprehensive help
help-all:
    @just helptext::all

# Main help entry point
help:
    @just helptext::main

# Help with topic argument
help-topic TOPIC:
    @just helptext::topic {{TOPIC}}

# Alias h that redirects to help with arguments
h *TOPIC:
    #!/usr/bin/env bash
    TOPIC="{{TOPIC}}"
    if [ -n "$TOPIC" ]; then
        just help-topic "$TOPIC"
    else
        just help
    fi

# Logo (delegated to help module)
logo:
    @just helptext::logo

server-browser-logs:
    @just dev::server-browser-logs

browser-tools-server:
    @just dev::browser-tools-server

browser-logs page="/":
    @just dev::browser-logs {{page}}

# =============================================================================
# LOCAL INSTALL
# =============================================================================
local-install:
    #!/usr/bin/env nu
    let nickel_path = $"{{ rustelo_root }}/resources/nickel:{{ justfile_directory() }}/site/config"
    let site_cfg    = "{{ justfile_directory() }}/site/config/index.ncl"

    print "→ building rustelo-htmx-server (release, htmx-ssr)"
    with-env { NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
        ^cargo build --release -p rustelo-htmx-server --no-default-features --features htmx-ssr
    }

    let cargo_cfg  = "{{ justfile_directory() }}/.cargo/config.toml"
    let target_dir = (
        $env.CARGO_TARGET_DIR? | default (
            if ($cargo_cfg | path exists) {
                (open $cargo_cfg).build?."target-dir"? | default "{{ justfile_directory() }}/target"
            } else {
                "{{ justfile_directory() }}/target"
            }
        )
    )

    let home       = $env.HOME
    let libexec    = ($home | path join ".local/libexec")
    let bin_dir    = ($home | path join ".local/bin")
    let share_dir  = ($home | path join ".local/share/rustelo-htmx-server")
    let nickel_dir = ($home | path join ".local/share/rustelo/nickel")

    mkdir $libexec $bin_dir $share_dir $nickel_dir

    print "→ installing binary → ~/.local/libexec/rustelo-htmx-server"
    cp ($target_dir | path join "release/rustelo-htmx-server") ($libexec | path join "rustelo-htmx-server")

    print "→ syncing rustelo nickel → ~/.local/share/rustelo/nickel"
    ^rsync -a --delete $"{{ rustelo_root }}/resources/nickel/" $"($nickel_dir)/"

    print "→ assembling htmx templates"
    let tmpl_out = ($target_dir | path join "site/htmx-templates")
    nu "{{ justfile_directory() }}/scripts/build/assemble-htmx-templates.nu" --out $tmpl_out

    print "→ installing templates → ~/.local/share/rustelo-htmx-server/htmx-templates"
    ^rsync -a --delete $"($tmpl_out)/" ($share_dir | path join "htmx-templates/")

    print "→ installing htmx runtime → ~/.local/share/rustelo-htmx-server/htmx"
    ^rsync -a --delete $"{{ rustelo_root }}/templates/shared/htmx/" ($share_dir | path join "htmx/")

    print "→ building gen-content-graph (release, gen)"
    let gen_manifest = $"{{ rustelo_root }}/crates/foundation/crates/rustelo_content_graph_ssr/Cargo.toml"
    with-env { CARGO_TARGET_DIR: $target_dir, NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
        ^cargo build --release -p rustelo_content_graph_ssr --features gen --manifest-path $gen_manifest
    }

    print "→ installing gen-content-graph → ~/.local/bin/gen-content-graph"
    let gen_dst = ($bin_dir | path join "gen-content-graph")
    cp ($target_dir | path join "release/gen-content-graph") $gen_dst
    ^chmod 755 $gen_dst

    # content_processor: the REAL generator is the framework's rustelo_server bin
    # (requires content-static); the project's same-named bin is a stub. Build it
    # into rustelo's own target dir to avoid colliding with that stub in our target.
    print "→ building content_processor (release, content-static)"
    let rustelo_cargo_cfg = $"{{ rustelo_root }}/.cargo/config.toml"
    let rustelo_target = (
        if ($rustelo_cargo_cfg | path exists) {
            (open $rustelo_cargo_cfg).build?."target-dir"? | default $"{{ rustelo_root }}/target"
        } else {
            $"{{ rustelo_root }}/target"
        }
    )
    let cp_manifest = $"{{ rustelo_root }}/crates/foundation/crates/rustelo_server/Cargo.toml"
    with-env { CARGO_TARGET_DIR: $rustelo_target, NICKEL_IMPORT_PATH: $nickel_path, SITE_CONFIG_PATH: $site_cfg } {
        ^cargo build --release -p rustelo_server --bin content_processor --features content-static --manifest-path $cp_manifest
    }

    print "→ installing content_processor → ~/.local/libexec/content_processor"
    cp ($rustelo_target | path join "release/content_processor") ($libexec | path join "content_processor")

    print "→ writing content_processor wrapper → ~/.local/bin/content_processor"
    let cp_wrapper = ($bin_dir | path join "content_processor")
    cp "{{ justfile_directory() }}/scripts/local-install/content_processor.sh" $cp_wrapper
    ^chmod 755 $cp_wrapper

    print "→ writing wrapper → ~/.local/bin/rustelo-htmx-server"
    let wrapper_dst = ($bin_dir | path join "rustelo-htmx-server")
    cp "{{ justfile_directory() }}/scripts/local-install/rustelo-htmx-server.sh" $wrapper_dst
    ^chmod 755 $wrapper_dst

    print ""
    print "✅ done — usage:"
    print "   rustelo-htmx-server                # auto-detects site/ in $PWD"
    print "   rustelo-htmx-server --site <path>  # explicit workspace root"
    print $"   # or place site tree at ($home)/.config/rustelo-htmx-server/site/"
    print "   gen-content-graph                  # regenerate content_graph.json (needs ONTOREF_NICKEL_IMPORT_PATH)"
    print "   content_processor --content-type blog   # regenerate site/r content indexes from site/content"

# =============================================================================
# CONTENT TOOLKIT INSTALL
# Installs the Nushell content-authoring scripts to ~/.local/share/rustelo-content
# and the `rustelo-content` dispatcher to ~/.local/bin. Sites consume the toolkit
# via the content.just module (mod content) without a source checkout alongside.
# =============================================================================
content-install:
    #!/usr/bin/env nu
    let home  = $env.HOME
    let share = ($home | path join ".local/share/rustelo-content")
    let bin   = ($home | path join ".local/bin")
    mkdir $share $bin

    print "→ installing content scripts → ~/.local/share/rustelo-content"
    (^rsync -a --delete
        --exclude "old/" --exclude ".image-spend.jsonl" --exclude "pending/"
        $"{{ justfile_directory() }}/scripts/content/" $"($share)/")

    print "→ writing dispatcher → ~/.local/bin/rustelo-content"
    let dst = ($bin | path join "rustelo-content")
    cp "{{ justfile_directory() }}/scripts/local-install/rustelo-content.sh" $dst
    ^chmod 755 $dst

    print ""
    print "✅ done — usage from a site root (dir containing site/):"
    print "   rustelo-content images --dry-run --type blog"
    print "   rustelo-content sync validate-translations"
    print "   # via just module:  mod content \".just/content.just\"  →  just content images"

# =============================================================================
# AUTHORING SETUP
# Wire a consumer site to the authoring toolkit: ensure the rustelo-content CLI
# is installed, symlink this repo's content.just into <site>/.just/ (relative,
# portable), and add `mod authoring` to the site justfile. Idempotent.
#   just authoring-setup /path/to/site      (defaults to PWD)
# =============================================================================
authoring-setup site=".":
    #!/usr/bin/env nu
    let src_module = ("{{ justfile_directory() }}/justfiles/content.just" | path expand)
    let site       = ("{{ site }}" | path expand)
    let site_just  = ($site | path join "justfile")
    if not ($site_just | path exists) {
        error make { msg: $"No justfile at ($site) — pass a site root: just authoring-setup <path>" }
    }

    # 1. ensure the CLI is on PATH
    if (which rustelo-content | is-empty) {
        print "→ rustelo-content not found; running content-install"
        just content-install
    } else {
        print "→ rustelo-content already on PATH"
    }

    # 2. symlink the module into <site>/.just/ with a relative target (survives
    #    relocating the whole tree; symlink target is relative to the link's dir)
    let just_dir = ($site | path join ".just")
    mkdir $just_dir
    let link = ($just_dir | path join "content.just")
    let from = ($just_dir   | path split)
    let to   = ($src_module | path split)
    let n    = ([($from | length) ($to | length)] | math min)
    mut i = 0
    while $i < $n and ($from | get $i) == ($to | get $i) { $i = $i + 1 }
    let ups = (0..<(($from | length) - $i) | each { ".." })
    let rel = (($ups ++ ($to | skip $i)) | path join)
    ^ln -sf $rel $link
    print $"→ linked ($link) → ($rel)"

    # 3. ensure `mod authoring` in the site justfile (idempotent)
    let txt = (open $site_just)
    if ($txt | str contains "mod authoring") {
        print "→ mod authoring already present"
    } else {
        let lines = ($txt | lines)
        let assign_rows = ($lines | enumerate | where { |e|
            let t = ($e.item | str trim)
            (not ($t | str starts-with "#")) and ($t | str contains ":=")
        })
        let set_rows = ($lines | enumerate | where { |e| ($e.item | str trim | str starts-with "set ") })
        let anchor = if (not ($assign_rows | is-empty)) {
            ($assign_rows | last | get index)
        } else if (not ($set_rows | is-empty)) {
            ($set_rows | last | get index)
        } else { -1 }

        let modline = 'mod authoring ".just/content.just"'
        let comment = '# Content-authoring toolkit (rustelo-content): images | sync | validate | new'
        let new_lines = if $anchor < 0 {
            ([$comment $modline ""] ++ $lines)
        } else {
            ($lines | enumerate | each { |e|
                if $e.index == $anchor { [$e.item "" $comment $modline] } else { [$e.item] }
            } | flatten)
        }
        $new_lines | str join "\n" | save --force $site_just
        print $"→ added `mod authoring` to ($site_just)"
    }

    print ""
    print "✅ site wired — run:  just authoring images --dry-run --type blog"

# =============================================================================
# AUTHORING SETUP — BATCH
# Discover every justfile under <root> that targets this source repo (references
# "website-htmx-rustelo") and wire each site via authoring-setup. <root> defaults
# to the dir two levels above this repo (the shared Development tree). Idempotent.
#   just authoring-setup-all [root]
# =============================================================================
authoring-setup-all root="":
    #!/usr/bin/env nu
    if (which rg | is-empty) {
        error make { msg: "ripgrep (rg) is required for site discovery" }
    }
    let src_root = ("{{ justfile_directory() }}" | path expand)
    let root = if ("{{ root }}" | is-empty) {
        ($src_root | path dirname | path dirname)
    } else {
        ("{{ root }}" | path expand)
    }
    print $"→ scanning ($root) for sites targeting website-htmx-rustelo"

    # rg exit 1 = no matches (benign); >1 = real error
    let scan = (^rg -l --no-ignore -g "justfile" "website-htmx-rustelo" $root | complete)
    if $scan.exit_code > 1 {
        error make { msg: $"rg failed: ($scan.stderr)" }
    }

    let sites = (
        $scan.stdout | lines
        | where { |l| ($l | str trim) != "" }
        | where { |l| not (($l | path expand) | str starts-with $src_root) }
        | each { |j| $j | path expand | path dirname }
        | uniq
    )
    if ($sites | is-empty) {
        print "  no consumer sites found."
        return
    }

    print $"  found ($sites | length) site\(s\):"
    $sites | each { |s| print $"    ($s)" }
    print ""
    for s in $sites {
        print $"━━ wiring ($s)"
        just -f $"{{ justfile_directory() }}/justfile" authoring-setup $s
    }
    print ""
    print $"✅ wired ($sites | length) site\(s\)"
