# Tuicker project commands

# Display available recipes
[group('user')]
help:
    @just --list --unsorted

# Install tuick using uv tool
[group('user')]
install:
    uv tool install --refresh-package tuick file://.

# Common variables

[private]
_run-dev := 'uv run --dev'
[private]
_python-dirs := "src tests scripts"
[private]
_pytest-opts := "--no-header --tb=short"

# Bash definitions

[private]
_bash-defs := '''
run_dev="''' + _run-dev + '''"
python_dirs="''' + _python-dirs + '''"
full_diff=''' + full-diff + ''';
COMMAND="''' + style('command') + '''"
NORMAL="''' + NORMAL + '''"
safe () { "$@" || status=false; }
end-safe () { ${status:-true}; }
show () { echo "$COMMAND$*$NORMAL"; }
visible () { show "$@"; "$@"; }
report () { local s=$?; show "$@"; echo "$out"; return $s; }
quiet () { out=$("$@" >&1) || report "$@"; }
pytest-agent-filter () {
    $full_diff && while read -r line
    do [[ $line =~ ^===+\ FAILURES\ ===+$ ]] && break; done
    cat
}
tuickf="tuick --format"
'''
# Pytest options.
# Run with full-diff=true for full diffs.

full-diff := 'false'
[private]
_diff-limit-opt := ' -o truncation_limit_lines=7'
[private]
_pytest-diff-opt := if full-diff == 'true' { ' --verbose' } else { ' --quiet' + _diff-limit-opt }
[private]
_pytest-agent-opts := _pytest-opts + " -p no:icdiff" + _pytest-diff-opt

# Development workflow: check, test, ARGS passed to pytest
[group('dev')]
dev *ARGS: _fail_if_claudecode compile
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }} {{ _check-body }}
    safe visible $run_dev $tuickf -- pytest {{ _pytest-opts }} {{ ARGS }}
    end-safe

# Agent workflow: check, test with minimal output, ARGS passed to pytest
[group('agent')]
agent *ARGS: agent-compile
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }} {{ _agent-check-body }}
    safe quiet {{ _run-dev }} pytest {{ _pytest-agent-opts }} {{ ARGS }} \
    | pytest-agent-filter \
    || $full_diff || echo 'For full diff: just full-diff=true agent"'
    end-safe && echo OK

# Clean build files
[group('dev')]
clean:
    rm -rf .venv */__pycache__ */*/__pycache__ build dist list */*.so */*/*.so

# Clean non-build caches and run files
[group('dev')]
clean-cache:
    rm -rf .dmypy.json .mypy_cache .pytest_cache .ruff_cache

# Fail if CLAUDECODE is set
[no-exit-message]
[private]
_fail_if_claudecode:
    #!/usr/bin/env bash -euo pipefail
    if [ "${CLAUDECODE:-}" != "" ]; then
        echo -e '{{ style("error") }}⛔️ Denied: use agent recipes{{ NORMAL }}'
        exit 1
    fi

_python_format := \
"""-p '%E*** Error ' -p '%C  File "%f", line %l' -p '%C%m' -p '%Z'"""

# Compile python files, quick test for valid syntax
[group('dev')]
[private]
compile:
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }}
    compile="python3 -m compileall -q $python_dirs"
    show $run_dev $tuickf -- $compile
    $run_dev $tuickf {{ _python_format }} -- $compile

# Compile python files, with less output
[group('agent')]
[no-exit-message]
[private]
agent-compile:
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }}
    quiet {{ _run-dev }} -m compileall -q {{ _python-dirs }}

# Run test suite, ARGS passed to pytest
[group('dev')]
test *ARGS: _fail_if_claudecode
    {{ _run-dev }} tuick --format -- pytest {{ _pytest-opts }} {{ ARGS }}

# Run test suite, with less output, ARGS passed to pytest
[group('agent')]
[no-exit-message]
agent-test *ARGS:
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }}
    quiet {{ _run-dev }} pytest {{ _pytest-agent-opts }} {{ ARGS }} \
    | pytest-agent-filter \
    && { {{ is_dependency() }} || echo OK; } \
    || { $full_diff || echo 'For full diff: just full-diff=true agent-test';
         false; }

# Static code analysis and style checks
[group('dev')]
check: _fail_if_claudecode compile
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }} {{ _check-body }}
    end-safe

[private]
_check-body := '''
    safe visible $run_dev $tuickf -- ruff format --check --quiet $python_dirs
    safe visible $run_dev $tuickf -p "%E%f" -- docformatter --check $python_dirs
    safe visible $run_dev $tuickf -- ruff check --quiet $python_dirs
    safe visible $run_dev $tuickf -- mypy
    safe visible $run_dev make -C agents check
'''

# Report TODO, FIXME, XXX, HACK comments
[group('dev')]
todo:
    {{ _run-dev }} ruff check --quiet --ignore ALL --select FIX \
        {{ _python-dirs }}

# Static code analysis and style checks, with less output
[group('agent')]
[no-exit-message]
agent-check: agent-compile
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }} {{ _agent-check-body }}
    end-safe && { {{ is_dependency() }} ||  echo OK; }

[private]
_agent-check-body := '''
    safe quiet $run_dev ruff format --check --quiet $python_dirs \
    || { echo 'Try "just format"'; status=false; }
    safe quiet $run_dev docformatter --check $python_dirs
    safe quiet $run_dev ruff check --quiet --output-format=concise $python_dirs
    safe quiet $run_dev mypy
    safe quiet $run_dev make -C agents check
'''

# Ruff auto-fix
[group('dev')]
ruff-fix *ARGS:
    {{ _run-dev }} ruff check --quiet --output-format=concise --fix \
    {{ ARGS }} {{ _python-dirs }}

# Reformat code, fail if formatting errors remain
[group('dev')]
format:
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }}
    safe visible $run_dev ruff format $python_dirs
    safe visible $run_dev ruff check --fix --unsafe-fixes --fix-only $python_dirs
    safe visible $run_dev docformatter --in-place $python_dirs
    end-safe

# Create release: tag, build tarball, upload to PyPI and GitHub
[group('dev')]
release bump='patch': _fail_if_claudecode
    #!/usr/bin/env bash -euo pipefail
    {{ _bash-defs }}
    ERROR="{{ style('error') }}"
    GREEN=$'\033[32m'  # ansi code for green
    fail () { echo "${ERROR}$*${NORMAL}"; exit 1; }
    git diff --quiet HEAD || fail "Error: uncommitted changes"
    new_version=$(uv version --bump {{ bump }} --dry-run)
    while read -re -p "Release $new_version? [y/n] " answer; do
        case "$answer" in
            y|Y) break;;
            n|N) exit 1;;
            *) continue;;
        esac
    done
    version=$(uv version --bump {{ bump }})
    git add pyproject.toml uv.lock
    visible git commit -m "🔖 Release $version"
    visible git push
    tag="v$(uv version --short)"
    visible just dev -qq
    git rev-parse "$tag" >/dev/null 2>&1 \
    && fail "Error: tag $tag already exists"
    visible git tag -a "$tag" -m "Release $version"
    visible git push origin "$tag"
    visible uv build
    visible uv publish
    visible gh release create "$tag" --title "Release $version" \
        --generate-notes
    echo "${GREEN}Release $tag complete${NORMAL}"

# Find new multi-line expressions for line-counter analysis
[group('agent')]
agent-new-multi:
    uv run --dev scripts/find_multiline_exprs.py
