#!/usr/bin/env bash
set -uo pipefail

usage() {
    cat <<'EOF'
Usage: semver-ci [OPTIONS]

Compute the next MAJOR.MINOR.PATCH.BUILD from commit-message tokens since the
last release, maintain a VERSION.md file, and optionally tag / push / create a
Gitea release. Non-main branches get MAJOR.MINOR.PATCH.BUILD-BRANCH-SHORTSHA
and never produce a release.

Options:
  -n, --dryrun              Preview actions without side effects
      --tag                 Create the tag locally (main-only)
      --push                Push the tag to origin (implies --tag, main-only)
      --release             Create Gitea release via API (implies --push, main-only)
      --build N             Override BUILD number
      --base TAG            Override last-release tag auto-detection
      --branch NAME         Override current-branch detection
      --main-branch NAME    Name of the main branch (default: main)
      --version-file PATH   Path to the version file (default: <repo>/VERSION.md)
      --no-version-file     Skip writing the version file
  -h, --help                Show this help

Environment (read when present):
  GITHUB_RUN_NUMBER / GITEA_RUN_NUMBER  BUILD source
  GITHUB_REF_NAME                       Branch name
  GITHUB_SHA                            Full commit SHA
  GITHUB_OUTPUT                         Step outputs file (append-mode)
  GITEA_TOKEN / GITHUB_TOKEN            Auth for --release
  GITHUB_SERVER_URL / GITHUB_REPOSITORY Target server and repo for --release
EOF
}

dryrun=false
do_tag=false
do_push=false
do_release=false
build_override=""
base_override=""
branch_override=""
main_branch="main"
version_file_override=""
write_version_file=true

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help) usage; exit 0 ;;
        -n|--dryrun) dryrun=true; shift ;;
        --tag) do_tag=true; shift ;;
        --push) do_tag=true; do_push=true; shift ;;
        --release) do_tag=true; do_push=true; do_release=true; shift ;;
        --build)
            [[ $# -lt 2 ]] && { echo "Error: --build requires a value" >&2; exit 1; }
            build_override="$2"; shift 2 ;;
        --base)
            [[ $# -lt 2 ]] && { echo "Error: --base requires a value" >&2; exit 1; }
            base_override="$2"; shift 2 ;;
        --branch)
            [[ $# -lt 2 ]] && { echo "Error: --branch requires a value" >&2; exit 1; }
            branch_override="$2"; shift 2 ;;
        --main-branch)
            [[ $# -lt 2 ]] && { echo "Error: --main-branch requires a value" >&2; exit 1; }
            main_branch="$2"; shift 2 ;;
        --version-file)
            [[ $# -lt 2 ]] && { echo "Error: --version-file requires a value" >&2; exit 1; }
            version_file_override="$2"; shift 2 ;;
        --no-version-file) write_version_file=false; shift ;;
        -*) echo "Error: Unknown option: $1" >&2; exit 1 ;;
        *)  echo "Error: Unexpected argument: $1" >&2; exit 1 ;;
    esac
done

# --- git repo sanity ---

if ! git rev-parse --git-dir >/dev/null 2>&1; then
    echo "Error: not inside a git repository" >&2
    exit 1
fi

if ! git rev-parse --verify HEAD >/dev/null 2>&1; then
    echo "Error: repository has no commits" >&2
    exit 1
fi

repo_root="$(git rev-parse --show-toplevel)"

# --- branch & commit detection ---

if [[ -n "$branch_override" ]]; then
    branch="$branch_override"
elif [[ -n "${GITHUB_REF_NAME:-}" ]]; then
    branch="$GITHUB_REF_NAME"
else
    branch="$(git rev-parse --abbrev-ref HEAD)"
fi

if [[ -n "${GITHUB_SHA:-}" ]]; then
    short_sha="${GITHUB_SHA:0:7}"
else
    short_sha="$(git rev-parse --short=7 HEAD)"
fi

is_main=false
if [[ "$branch" == "$main_branch" ]]; then
    is_main=true
fi

# --- last release tag discovery ---

TAG_RE='^v[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'

if [[ -n "$base_override" ]]; then
    if ! git rev-parse --verify "$base_override" >/dev/null 2>&1; then
        echo "Error: --base tag does not exist: $base_override" >&2
        exit 1
    fi
    last_tag="$base_override"
else
    last_tag="$(git tag -l 'v*' | grep -E "$TAG_RE" | sort -V | tail -n 1 || true)"
fi

if [[ -z "$last_tag" ]]; then
    maj=0; min=0; pat=0
    log_range="HEAD"
else
    ver="${last_tag#v}"
    IFS='.' read -r maj min pat _build <<< "$ver"
    log_range="${last_tag}..HEAD"
fi

# --- commit scanning ---

commits="$(git log "$log_range" --format=%B 2>/dev/null || true)"
commit_count="$(git log "$log_range" --oneline 2>/dev/null | wc -l | tr -d ' ' || true)"
commit_count="${commit_count:-0}"

count_trigger() {
    local token="$1"
    # Whole-line match, whitespace-tolerant; count matching lines.
    printf '%s\n' "$commits" | grep -cE "^[[:space:]]*${token}[[:space:]]*\$" || true
}

count_major="$(count_trigger NEW_MAJOR)"
count_minor="$(count_trigger NEW_MINOR)"
count_patch="$(count_trigger NEW_PATCH)"

bump="none"
released=false
if [[ "$count_major" -gt 0 ]]; then
    maj=$((maj + 1)); min=0; pat=0
    bump="MAJOR"; released=true
elif [[ "$count_minor" -gt 0 ]]; then
    min=$((min + 1)); pat=0
    bump="MINOR"; released=true
elif [[ "$count_patch" -gt 0 ]]; then
    pat=$((pat + 1))
    bump="PATCH"; released=true
fi

# --- BUILD number ---

if [[ -n "$build_override" ]]; then
    build="$build_override"
elif [[ -n "${GITHUB_RUN_NUMBER:-}" ]]; then
    build="$GITHUB_RUN_NUMBER"
elif [[ -n "${GITEA_RUN_NUMBER:-}" ]]; then
    build="$GITEA_RUN_NUMBER"
else
    build="$(git rev-list --count HEAD 2>/dev/null || echo "")"
fi

if [[ -z "$build" ]]; then
    echo "Error: Cannot determine BUILD number. Set GITHUB_RUN_NUMBER or pass --build N." >&2
    exit 1
fi

if ! [[ "$build" =~ ^[0-9]+$ ]]; then
    echo "Error: BUILD number must be a non-negative integer, got: $build" >&2
    exit 1
fi

# --- compose version ---

if $is_main; then
    version="${maj}.${min}.${pat}.${build}"
    new_tag="v${version}"
else
    safe_branch="${branch//\//-}"
    version="${maj}.${min}.${pat}.${build}-${safe_branch}-${short_sha}"
    new_tag=""
fi

# --- version file path ---

if [[ -n "$version_file_override" ]]; then
    version_file="$version_file_override"
else
    version_file="${repo_root}/VERSION.md"
fi

generated="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

render_version_file() {
    cat <<EOF
# Version

**${version}**

- Major: ${maj}
- Minor: ${min}
- Patch: ${pat}
- Build: ${build}
- Branch: ${branch}
- Commit: ${short_sha}
- Tag: ${new_tag:--}
- Generated: ${generated}
EOF
}

# --- dryrun output ---

if $dryrun; then
    if $is_main; then
        echo "[dryrun] Branch: ${branch} (main-branch detected)"
    else
        echo "[dryrun] Branch: ${branch} (non-main)"
    fi
    echo "[dryrun] Commit: ${short_sha}"
    echo "[dryrun] Last release: ${last_tag:-<none>}"
    echo "[dryrun] Commits scanned: ${commit_count}"

    triggers=()
    [[ "$count_major" -gt 0 ]] && triggers+=("NEW_MAJOR (${count_major})")
    [[ "$count_minor" -gt 0 ]] && triggers+=("NEW_MINOR (${count_minor})")
    [[ "$count_patch" -gt 0 ]] && triggers+=("NEW_PATCH (${count_patch})")
    if [[ ${#triggers[@]} -eq 0 ]]; then
        echo "[dryrun] Triggers found: none"
    else
        echo "[dryrun] Triggers found: $(IFS=', '; echo "${triggers[*]}")"
    fi

    echo "[dryrun] Version bump: ${bump}"
    echo "[dryrun] New version: ${version}"

    if $write_version_file; then
        echo "[dryrun] Would write version file: ${version_file}"
    fi

    if $is_main; then
        $do_tag && echo "[dryrun] Would create tag: ${new_tag}"
        $do_push && echo "[dryrun] Would push tag to origin"
        if $do_release; then
            if $released; then
                echo "[dryrun] Would create Gitea release for ${new_tag}"
            else
                echo "[dryrun] Would skip release (no MAJOR/MINOR/PATCH change)"
            fi
        fi
    else
        if $do_tag || $do_push || $do_release; then
            echo "[dryrun] Would skip tag/push/release on non-main branch"
        fi
    fi
    exit 0
fi

# --- real mode ---

echo "$version"

if [[ -n "${GITHUB_OUTPUT:-}" ]]; then
    {
        echo "version=${version}"
        echo "tag=${new_tag}"
        echo "released=${released}"
        echo "branch=${branch}"
        echo "is_main=${is_main}"
    } >> "$GITHUB_OUTPUT"
fi

if $write_version_file; then
    vf_dir="$(dirname "$version_file")"
    if [[ ! -d "$vf_dir" ]]; then
        echo "Error: version file directory does not exist: $vf_dir" >&2
        exit 1
    fi
    render_version_file > "$version_file"
fi

if ! $is_main; then
    if $do_tag || $do_push || $do_release; then
        echo "semver-ci: skipping tag/push/release on non-main branch '${branch}'" >&2
    fi
    exit 0
fi

if $do_tag; then
    if git rev-parse -q --verify "refs/tags/${new_tag}" >/dev/null; then
        echo "Error: tag already exists: ${new_tag}" >&2
        exit 1
    fi
    git tag "$new_tag"
fi

if $do_push; then
    git push origin "$new_tag"
fi

if $do_release; then
    if ! $released; then
        echo "semver-ci: release skipped (no MAJOR/MINOR/PATCH change)" >&2
        exit 0
    fi
    token="${GITEA_TOKEN:-${GITHUB_TOKEN:-}}"
    if [[ -z "$token" ]]; then
        echo "Error: GITEA_TOKEN or GITHUB_TOKEN required for --release" >&2
        exit 1
    fi
    server="${GITHUB_SERVER_URL:-}"
    repo="${GITHUB_REPOSITORY:-}"
    if [[ -z "$server" || -z "$repo" ]]; then
        echo "Error: GITHUB_SERVER_URL and GITHUB_REPOSITORY must be set for --release" >&2
        exit 1
    fi
    api_url="${server%/}/api/v1/repos/${repo}/releases"
    body="$(printf '{"tag_name":"%s","name":"%s","body":"Automated release"}' "$new_tag" "$new_tag")"
    tmp_response="$(mktemp)"
    trap 'rm -f "$tmp_response"' EXIT
    http_code="$(curl -sS -o "$tmp_response" -w '%{http_code}' \
        -X POST "$api_url" \
        -H "Authorization: token ${token}" \
        -H "Content-Type: application/json" \
        -d "$body")" || { echo "Error: curl failed" >&2; exit 1; }
    if [[ "$http_code" != "201" ]]; then
        echo "Error: Gitea release creation failed (HTTP ${http_code})" >&2
        cat "$tmp_response" >&2
        exit 1
    fi
fi
