# Shared shell helpers for the cellpy repo.
# Source from the repo root:  source .aliases
# List what's here:           aliases

# Path of this file (set when sourced).
_CELLPY_ALIASES_FILE="${BASH_SOURCE[0]:-}"

# `aliases` — list functions and shell aliases from this file.
aliases() {
    local src="${_CELLPY_ALIASES_FILE:-}"
    if [ -z "$src" ] || [ ! -f "$src" ]; then
        # Fall back: repo-root .aliases when cwd is the project.
        if [ -f .aliases ]; then
            src=".aliases"
        else
            echo "aliases: cannot find .aliases (source it once from the repo)."
            return 1
        fi
    fi

    echo "From $src:"
    echo

    # Public top-level functions: name() { … } not starting with _.
    # Prefer the `# \`name …\`` / `# Opt-in: \`…\`` usage comment above each def.
    # Records: NAME<TAB>blurb
    local records
    records="$(
        awk '
            # Keep the *first* usage blurb in a comment block (do not overwrite
            # with later `# \`…\`` lines in the same doc paragraph).
            /^# `[^`]+`/ {
                if (comment == "") {
                    comment = $0
                    sub(/^# /, "", comment)
                }
                next
            }
            /^# Opt-in: `/ {
                if (comment == "") {
                    comment = $0
                    sub(/^# Opt-in: /, "", comment)
                }
                next
            }
            /^[a-zA-Z][a-zA-Z0-9_]*\(\)[ \t]*\{/ {
                name = $0
                sub(/\(\).*/, "", name)
                if (name !~ /^_/)
                    printf "%s\t%s\n", name, comment
                comment = ""
                next
            }
            /^[^#]/ {
                if ($0 !~ /^[ \t]*$/)
                    comment = ""
            }
        ' "$src"
    )"

    local name blurb
    while IFS=$'\t' read -r name blurb; do
        [ -z "$name" ] && continue
        if [ -n "$blurb" ]; then
            printf "  %-14s  %s\n" "$name" "$blurb"
        else
            printf "  %s\n" "$name"
        fi
    done <<< "$records"

    # Top-level `alias …` lines only (skip aliases nested inside functions).
    if grep -E '^alias[[:space:]]' "$src" >/dev/null 2>&1; then
        echo
        echo "Shell aliases defined in file:"
        grep -E '^alias[[:space:]]' "$src" | sed 's/^/  /'
    fi

    echo
    echo "Loaded in this shell:"
    while IFS=$'\t' read -r name blurb; do
        [ -z "$name" ] && continue
        if declare -F "$name" >/dev/null 2>&1; then
            echo "  $name"
        else
            echo "  $name  (not loaded — source .aliases)"
        fi
    done <<< "$records"
}

# Opt-in: `conda_shell` — wire conda into this shell (not run on source).
conda_shell() {
    case "$OSTYPE" in
        msys*|cygwin*)
            # Git Bash on Windows
            alias conda="C:/Users/jepe/AppData/Local/miniconda3/Scripts/conda.exe"
            eval "$(conda shell.bash hook)"
            ;;
        linux-gnu*)
            if [ -n "$WSL_DISTRO_NAME" ]; then
                # Source conda.sh so `conda activate` works (defines the conda shell function).
                [ -f "$HOME/miniconda3/etc/profile.d/conda.sh" ] && . "$HOME/miniconda3/etc/profile.d/conda.sh"
            fi
            ;;
        *)
            echo "conda_shell: unsupported OSTYPE='$OSTYPE'"
            return 1
            ;;
    esac
}

# `release [post|patch|minor|major|alpha|beta|rc|stable|<explicit-tag>]`
#
# Tag-derived releases (uv-dynamic-versioning). Version comes from the git tag;
# there is no `uv version --bump` / pyproject version edit.
#
#   release              # cut next same-stage bump (aN→a(N+1), else post)
#   release post         # cut vX.Y.Z.postN (or .post1 on a stable tag)
#   release patch        # cut next patch (strips post/pre → X.Y.(Z+1))
#   release minor|major  # same idea for minor/major
#   release alpha|beta|rc  # next pre that sorts AFTER last tag
#                          # (post/stable → X.Y.(Z+1)a1; aN → a(N+1); …)
#   release stable       # finalize current pre, or next patch if already final/post
#   release v2.1.1.post4 # cut an explicit tag (must start with v)
#
# Prints last tag + planned tag, runs guards, then asks before
# ``gh release create`` (`--target` = `master` for v2.*, `v1.x` for v1.*).
# See `.issueflows/04-designs-and-guides/release-procedure.md`.
release() {
    local level="${1:-}"

    git fetch --tags --prune --quiet 2>/dev/null || true

    local last
    last="$(git tag --sort=-v:refname | head -n1)"
    if [ -z "$last" ]; then
        echo "release: no tags found in this repo."; return 1
    fi
    echo "release: last tag = $last"

    # No args: follow last tag's stage (aN→alpha, bN→beta, rcN→rc, else post).
    if [ -z "$level" ]; then
        level=post
        case "$last" in
            *rc[0-9]*) level=rc ;;
            *b[0-9]*)  level=beta ;;
            *a[0-9]*)  level=alpha ;;
        esac
        echo "release: no level given — using same-stage default ($level)"
    fi

    local planned=""
    # Shared next-tag arithmetic (stdout = vX.Y.Z…).
    _release_plan_tag() {
        # Prefer uv's env (has packaging); fall back to bare python.
        local py=(uv run --no-project python)
        command -v uv >/dev/null 2>&1 || py=(python)
        "${py[@]}" - "$1" "$2" <<'PY'
import sys
from packaging.version import Version

last, level = sys.argv[1], sys.argv[2]
v = Version(last.lstrip("v"))
epoch = f"{v.epoch}!" if v.epoch else ""
base = list(v.release) + [0, 0, 0]
major, minor, patch = base[0], base[1], base[2]

def out(s: str) -> None:
    print("v" + s)

_PRE_ORDER = {"a": 0, "b": 1, "rc": 2}

def plan_pre(tag: str) -> None:
    """Plan a pre-release that sorts after *last* (PEP 440).

    After a final or ``.postN`` tag, ``X.Y.Za1`` is *older* than the last
    tag — bump the patch first. Same-stage pre increments N; advancing
    a→b→rc keeps the base; going backwards bumps the patch.
    """
    if v.pre and v.pre[0] == tag:
        n = v.pre[1] + 1
        out(f"{epoch}{major}.{minor}.{patch}{tag}{n}")
        return
    if v.is_prerelease:
        cur = _PRE_ORDER.get(v.pre[0], -1)
        nxt = _PRE_ORDER[tag]
        if nxt > cur:
            out(f"{epoch}{major}.{minor}.{patch}{tag}1")
        else:
            out(f"{epoch}{major}.{minor}.{patch + 1}{tag}1")
        return
    # final or post — next pre must be on the following patch
    out(f"{epoch}{major}.{minor}.{patch + 1}{tag}1")

if level == "major":
    out(f"{epoch}{major + 1}.0.0")
elif level == "minor":
    out(f"{epoch}{major}.{minor + 1}.0")
elif level == "patch":
    out(f"{epoch}{major}.{minor}.{patch + 1}")
elif level == "stable":
    # Finalize current pre, or next patch if already final/post.
    if v.is_prerelease:
        out(f"{epoch}{major}.{minor}.{patch}")
    else:
        out(f"{epoch}{major}.{minor}.{patch + 1}")
elif level == "post":
    n = (v.post or 0) + 1
    out(f"{epoch}{major}.{minor}.{patch}.post{n}")
elif level == "alpha":
    plan_pre("a")
elif level == "beta":
    plan_pre("b")
elif level == "rc":
    plan_pre("rc")
else:
    raise SystemExit(f"unknown level {level!r}")
PY
    }

    case "$level" in
        v*)
            planned="$level"
            ;;
        post|patch|minor|major|stable|alpha|beta|rc)
            planned="$(_release_plan_tag "$last" "$level")" || return 1
            ;;
        *)
            echo "release: unknown level '$level' (use post|patch|minor|major|alpha|beta|rc|stable|v…)"
            return 1
            ;;
    esac

    echo "release: planned tag = $planned"

    if [ -n "$(git status --porcelain)" ]; then
        echo "release: working tree is not clean (including untracked)."
        echo "         Promote HISTORY.md / commit release prep first; then re-run."
        git status --short
        return 1
    fi

    if git rev-parse -q --verify "refs/tags/$planned" >/dev/null 2>&1 \
       || git ls-remote --exit-code --tags origin "refs/tags/$planned" >/dev/null 2>&1; then
        echo "release: tag $planned already exists. Pick another level or an explicit tag."
        return 1
    fi

    local branch target
    branch="$(git branch --show-current)"
    case "$planned" in
        v1.*) target="v1.x" ;;
        v2.*) target="master" ;;
        *)
            echo "release: cannot infer --target from $planned (expected v1.* or v2.*)."
            return 1
            ;;
    esac

    if [ "$branch" != "$target" ]; then
        echo "release: current branch is '$branch' but $planned must be cut from '$target'."
        echo "         git switch $target && git pull --ff-only"
        return 1
    fi

    local prerelease_flag=()
    case "$planned" in
        *a[0-9]*|*b[0-9]*|*rc[0-9]*) prerelease_flag=(--prerelease) ;;
    esac

    echo "release: about to run:"
    echo "         gh release create $planned --target $target ${prerelease_flag[*]} --generate-notes"
    local reply=""
    if [ ! -t 0 ]; then
        echo "release: stdin is not a TTY — refusing to create without interactive confirm."
        echo "         Re-run in a terminal, or pass an explicit tag after checking the plan."
        return 1
    fi
    read -r -p "release: create this GitHub release? [y/N] " reply
    case "$reply" in
        y|Y|yes|YES) ;;
        *)
            echo "release: aborted."
            return 1
            ;;
    esac

    echo "release: creating GitHub release $planned --target $target ${prerelease_flag[*]}"
    gh release create "$planned" --target "$target" --generate-notes "${prerelease_flag[@]}"
}

# `acp "commit message"` — stage all, commit, push
acp() {
    if [ $# -lt 1 ]; then
        echo 'acp: usage: acp "commit message"'
        return 1
    fi
    git add -A && git commit -m "$1" && git push
}

alias uve="uv sync --extra batch --reinstall-package cellpy"
