#!/usr/bin/env just

set shell := ["nu", "-c"]
set windows-shell := ["nu", "-c"]
set positional-arguments := true
set allow-duplicate-variables := true

project_root := justfile_directory()
msrv := "1.92.0"

# ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ #
#      Recipes      #
# ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ #

[doc('List all available recipes')]
default:
    @just --list


[doc('Check workspace for compilation errors')]
[group('build')]
check *flags:
    @echo "🔎 Checking workspace..."
    cargo check --workspace {{ flags }}

[doc('Build workspace (all targets, debug)')]
[group('build')]
build *flags:
    @echo "🔨 Building workspace..."
    cargo build --workspace --all-targets {{ flags }}

[doc('Build workspace in release mode')]
[group('build')]
build-release *flags:
    @echo "🚀 Building workspace (release)..."
    cargo build --workspace --release {{ flags }}

[doc('Build Linux-specific crates')]
[group('build')]
build-linux:
    @echo "🐧 Building Linux crates..."
    cargo build -p gpui_linux

[doc('Build macOS-specific crates')]
[group('build')]
build-mac:
    @echo "🍎 Building macOS crates..."
    cargo build -p gpui_macos

[doc('Build Windows-specific crates')]
[group('build')]
build-windows:
    @echo "🪟 Building Windows crates..."
    cargo build -p gpui_windows

[doc('Build all GPUI examples')]
[group('build')]
build-examples:
    @echo "📐 Building examples..."
    cargo build --package gpui --examples

[doc('Check WASM target (stable) — requires wasm32-unknown-unknown target to be installed')]
[group('build')]
check-wasm:
    @echo "🕸️ Checking WASM target (stable)..."
    cargo check --target wasm32-unknown-unknown --no-default-features -p gpui_platform

[doc('Check WASM target with atomics (nightly) — requires nightly + rust-src component')]
[group('build')]
check-wasm-atomics:
    #!/usr/bin/env nu
    if ((which rustup | length) == 0) {
        print "⚠️  Skipping WASM atomics: rustup not installed (required for +nightly)"
        exit 0
    }
    print "🕸️ Checking WASM target with atomics (nightly)..."
    with-env {CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory,+mutable-globals"} {
        cargo +nightly -Zbuild-std=std,panic_abort check --target wasm32-unknown-unknown -p gpui_platform
    }

[doc('Check examples + WASM web package — requires wasm32-unknown-unknown target to be installed')]
[group('build')]
check-examples:
    @echo "📐 Checking examples..."
    cargo build --package gpui --examples
    cargo check --package gpui_web --target wasm32-unknown-unknown


[doc('Run all workspace unit and integration tests')]
[group('testing')]
test *flags:
    @echo "🧪 Running workspace tests..."
    cargo test --workspace --no-fail-fast {{ flags }}

[doc('Run all workspace doc tests')]
[group('testing')]
test-doc:
    @echo "🧪 Running doc tests..."
    cargo test --workspace --doc --no-fail-fast

[doc('Run tests with nextest (faster parallel runner)')]
[group('testing')]
test-nextest:
    @echo "🧪 Running tests with nextest..."
    cargo nextest run --workspace

[doc('Run workspace tests with a fixed seed for reproducibility')]
[group('testing')]
test-with-seed seed="12345":
    #!/usr/bin/env nu
    print $"🧪 Running tests with seed {{ seed }}..."
    with-env {SEED: '{{ seed }}'} { cargo test --workspace }

[doc('Run workspace tests with additional arguments')]
[group('testing')]
test-with +args:
    @echo "🧪 Running workspace tests with args..."
    cargo test --workspace -- {{ args }}


[doc('Format all Rust code in the workspace')]
[group('quality')]
fmt:
    @echo "💅 Formatting Rust code..."
    cargo fmt --all

[doc('Check Rust code formatting without modifying files')]
[group('quality')]
fmt-check:
    @echo "💅 Checking Rust code formatting..."
    cargo fmt --all -- --check

[doc('Lint with Clippy (all targets, deny warnings)')]
[group('quality')]
clippy *flags:
    @echo "🔍 Running Clippy..."
    cargo clippy --workspace --all-targets -- -D warnings {{ flags }}

[doc('Run Clippy for a specific target triple')]
[group('quality')]
clippy-target target:
    @echo "🔍 Running Clippy for target {{ target }}..."
    cargo clippy --workspace --all-targets --target {{ target }} -- -D warnings

[doc('Automatically fix Clippy lints where possible')]
[group('quality')]
clippy-fix:
    @echo "🩹 Fixing Clippy lints..."
    cargo clippy --workspace --all-targets --fix --allow-dirty --allow-staged -- -D warnings

[doc('Run typos spell checker')]
[group('quality')]
typos:
    @echo "🔤 Checking for typos..."
    typos

[doc('Check TOML formatting with taplo')]
[group('quality')]
taplo:
    @echo "📋 Checking TOML formatting..."
    taplo fmt --check

[doc('Check for unused dependencies')]
[group('quality')]
machete:
    @echo "⚙️ Checking for unused dependencies..."
    cargo machete

[doc('Check workspace against MSRV — requires {{ msrv }} toolchain to be installed')]
[group('quality')]
msrv-check:
    @echo "🦀 Checking MSRV ({{ msrv }})..."
    cargo +{{ msrv }} check --workspace

[doc('Run all quality checks: fmt-check, clippy, typos, taplo, machete')]
[group('quality')]
lint: fmt-check clippy typos taplo machete

[doc('Verify packages conform to workspace standards')]
[group('quality')]
check-packages:
    @echo "✅ Package conformity checks passed."


[doc('Run full CI suite locally — mirrors GitHub Actions, reports all failures before exiting')]
[group('ci')]
ci:
    #!/usr/bin/env nu
    def run-check [name: string, block: closure] {
        print $"\n▶ ($name)"
        let code = try { do $block; $env.LAST_EXIT_CODE } catch { 1 }
        if $code == 0 {
            print $"✅ ($name)"
            {name: $name, ok: true}
        } else {
            print $"❌ ($name)"
            {name: $name, ok: false}
        }
    }

    def available [tool: string] {
        (which $tool | length) > 0
    }

    def skip [name: string, reason: string] {
        print $"\n⚠️  Skipping ($name): ($reason)"
        {name: $name, ok: true}
    }

    let msrv = '{{ msrv }}'

    let results = [
        (run-check "cargo fmt" { cargo fmt --all -- --check })
        (run-check "cargo clippy" { cargo clippy --workspace --all-targets -- -D warnings })
        (run-check "cargo build" { cargo build --workspace --all-targets })
        (run-check "cargo test" { cargo test --workspace --no-fail-fast })
        (run-check "doc tests" { cargo test --workspace --doc --no-fail-fast })
        (run-check "WASM stable" {
            cargo check --target wasm32-unknown-unknown --no-default-features -p gpui_platform
        })
        (if (available "rustup") {
            run-check "WASM atomics" {
                with-env {CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory,+mutable-globals"} {
                    cargo +nightly -Zbuild-std=std,panic_abort check --target wasm32-unknown-unknown -p gpui_platform
                }
            }
        } else {
            skip "WASM atomics" "rustup not installed (required for +nightly)"
        })
        (run-check "check examples" {
            cargo build --package gpui --examples
            cargo check --package gpui_web --target wasm32-unknown-unknown
        })
        (if (available "typos") { run-check "typos" { typos } } else { skip "typos" "not installed" })
        (if (available "taplo") { run-check "taplo" { taplo fmt --check } } else { skip "taplo" "not installed" })
        (if (available "cargo-machete") { run-check "cargo machete" { cargo machete } } else { skip "cargo machete" "install: cargo install cargo-machete" })
        (if (available "rustup") {
            run-check $"MSRV ($msrv)" { run-external "cargo" $"+($msrv)" "check" "--workspace" }
        } else {
            skip $"MSRV ($msrv)" "rustup not installed"
        })
    ]

    let failures = ($results | where ok == false | get name)
    if ($failures | length) > 0 {
        let failed_list = ($failures | str join ", ")
        print $"\n❌ ($failures | length) checks failed: ($failed_list)"
        exit 1
    }
    print "\n✅ All checks passed!"

[doc('Run tidy checks only: fmt, clippy, typos, taplo, machete, MSRV')]
[group('ci')]
ci-tidy:
    #!/usr/bin/env nu
    def run-check [name: string, block: closure] {
        print $"\n▶ ($name)"
        let code = try { do $block; $env.LAST_EXIT_CODE } catch { 1 }
        if $code == 0 {
            print $"✅ ($name)"
            {name: $name, ok: true}
        } else {
            print $"❌ ($name)"
            {name: $name, ok: false}
        }
    }

    def available [tool: string] {
        (which $tool | length) > 0
    }

    def skip [name: string, reason: string] {
        print $"\n⚠️  Skipping ($name): ($reason)"
        {name: $name, ok: true}
    }

    let msrv = '{{ msrv }}'

    let results = [
        (run-check "cargo fmt" { cargo fmt --all -- --check })
        (run-check "cargo clippy" { cargo clippy --workspace --all-targets -- -D warnings })
        (if (available "typos") { run-check "typos" { typos } } else { skip "typos" "not installed" })
        (if (available "taplo") { run-check "taplo" { taplo fmt --check } } else { skip "taplo" "not installed" })
        (if (available "cargo-machete") { run-check "cargo machete" { cargo machete } } else { skip "cargo machete" "install: cargo install cargo-machete" })
        (if (available "rustup") {
            run-check $"MSRV ($msrv)" { run-external "cargo" $"+($msrv)" "check" "--workspace" }
        } else {
            skip $"MSRV ($msrv)" "rustup not installed"
        })
    ]

    let failures = ($results | where ok == false | get name)
    if ($failures | length) > 0 {
        let failed_list = ($failures | str join ", ")
        print $"\n❌ ($failures | length) checks failed: ($failed_list)"
        exit 1
    }
    print "\n✅ Tidy checks passed!"

[doc('Run build + unit tests + doc tests')]
[group('ci')]
ci-test:
    #!/usr/bin/env nu
    def run-check [name: string, block: closure] {
        print $"\n▶ ($name)"
        let code = try { do $block; $env.LAST_EXIT_CODE } catch { 1 }
        if $code == 0 {
            print $"✅ ($name)"
            {name: $name, ok: true}
        } else {
            print $"❌ ($name)"
            {name: $name, ok: false}
        }
    }

    let results = [
        (run-check "cargo build" { cargo build --workspace --all-targets })
        (run-check "cargo test" { cargo test --workspace --no-fail-fast })
        (run-check "doc tests" { cargo test --workspace --doc --no-fail-fast })
    ]

    let failures = ($results | where ok == false | get name)
    if ($failures | length) > 0 {
        let failed_list = ($failures | str join ", ")
        print $"\n❌ ($failures | length) checks failed: ($failed_list)"
        exit 1
    }
    print "\n✅ Tests passed!"

[doc('Check both WASM targets: stable and nightly atomics')]
[group('ci')]
ci-wasm: check-wasm check-wasm-atomics


[doc('Clear the target directory if it exceeds a size threshold in MB')]
[group('maintenance')]
clear-target threshold_mb="300" target_dir="target":
    #!/usr/bin/env nu
    let threshold_mb = ('{{ threshold_mb }}' | into int)
    let target_dir = '{{ target_dir }}'

    if not ($target_dir | path exists) {
        print $"Target directory '($target_dir)' does not exist, skipping."
        exit 0
    }

    let size_bytes = (du $target_dir | get physical | first | into int)
    let size_mb = ($size_bytes / 1048576 | into int)

    print $"Target directory size: ($size_mb) MB [threshold: ($threshold_mb) MB]"

    if $size_mb > $threshold_mb {
        print "Exceeds threshold, clearing build caches..."

        let debug_dir = ($target_dir | path join "debug")
        if ($debug_dir | path exists) {
            let stale = (
                [
                    (glob $"($debug_dir)/**/*.d")
                    (glob $"($debug_dir)/**/*.o")
                    (glob $"($debug_dir)/**/*.rlib")
                ] | flatten | first 10000
            )
            $stale | each { |f| rm --force $f }
        }

        print "Cleanup complete."
    } else {
        print "Target directory is within acceptable size."
    }

[doc('Update Cargo dependencies')]
[group('maintenance')]
update:
    @echo "🔄 Updating dependencies..."
    cargo update

[doc('Show outdated dependencies')]
[group('maintenance')]
outdated:
    @echo "📋 Checking for outdated dependencies..."
    cargo outdated

[doc('Clean build artifacts')]
[group('maintenance')]
clean:
    @echo "🧹 Cleaning build artifacts..."
    cargo clean


[doc('Publish GPUI crates to crates.io in dependency order (dry="true" for a dry run)')]
[group('release')]
publish dry="false":
    #!/usr/bin/env nu
    let dry_run = ('{{ dry }}' == "true")

    let status = (^git status --porcelain | str trim)
    if ($status | str length) > 0 {
        error make {msg: "Working directory is not clean. Commit or stash changes before publishing."}
    }

    if not ("CARGO_REGISTRY_TOKEN" in $env) {
        error make {msg: "CARGO_REGISTRY_TOKEN is not set"}
    }

    # Topological publish order. Vendored support crates first, then core,
    # then renderers/platform leaves, with gpui_platform LAST (it has
    # target-conditional deps on every platform crate).
    let crates = [
        "crates/gpui_ce_util/Cargo.toml"
        "crates/gpui_collections/Cargo.toml"
        "crates/gpui_derive_refineable/Cargo.toml"
        "crates/gpui_refineable/Cargo.toml"
        "crates/gpui_sum_tree/Cargo.toml"
        "crates/gpui_scheduler/Cargo.toml"
        "crates/gpui_media/Cargo.toml"
        "crates/gpui_zed_util/Cargo.toml"

        # core
        "crates/gpui_shared_string/Cargo.toml"
        "crates/gpui_macros/Cargo.toml"
        "crates/gpui/Cargo.toml"

        # renderers / platform leaves
        "crates/gpui_wgpu/Cargo.toml"
        "crates/gpui_tokio/Cargo.toml"
        "crates/gpui_macos/Cargo.toml"
        "crates/gpui_linux/Cargo.toml"
        "crates/gpui_windows/Cargo.toml"
        "crates/gpui_web/Cargo.toml"
        "crates/gpui_elements/Cargo.toml"

        "crates/gpui_platform/Cargo.toml"
    ]

    let dry_flag = if $dry_run { ["--dry-run"] } else { [] }

    for manifest in $crates {
        let name = ($manifest | path dirname | path basename)
        print $"\n📦 Publishing ($name)..."
        run-external "cargo" "publish" "--manifest-path" $manifest "--allow-dirty" ...$dry_flag
        if $env.LAST_EXIT_CODE != 0 {
            error make {msg: $"Failed to publish ($name)"}
        }
    }

    print "\n✅ All crates published!"


[doc('Sync upstream Zed GPUI changes into this fork (local-only, never pushes)')]
[group('sync')]
sync-upstream *args:
    @python3 {{ project_root }}/scripts/sync-upstream/sync_upstream.py sync {{ args }}

[doc('One-time: record the upstream baseline to sync from (defaults to the pinned zed dep rev)')]
[group('sync')]
sync-upstream-bootstrap *args:
    @python3 {{ project_root }}/scripts/sync-upstream/sync_upstream.py bootstrap {{ args }}

[doc('Show how far behind upstream Zed GPUI this fork is')]
[group('sync')]
sync-upstream-status:
    @python3 {{ project_root }}/scripts/sync-upstream/sync_upstream.py status


[doc('Generate project documentation')]
[group('docs')]
doc *flags:
    @echo "📚 Generating documentation..."
    cargo doc --workspace --no-deps {{ flags }}

[doc('Generate and open project documentation in browser')]
[group('docs')]
doc-open:
    @echo "📚 Opening documentation in browser..."
    cargo doc --workspace --no-deps --open


[doc('Run a specific example by name')]
[group('development')]
example name:
    @echo "▶️ Running example {{ name }}..."
    cargo run --example {{ name }}

alias b   := build
alias br  := build-release
alias c   := check
alias t   := test
alias td  := test-doc
alias f   := fmt
alias fc  := fmt-check
alias clp := clippy
alias clf := clippy-fix
alias l   := lint
alias d   := doc
alias up  := update
alias cl  := clean
alias sync := sync-upstream
