#!/usr/bin/env bash
#
# DreamStack Release Script
#
# Reads .changeset/*.md files (YAML frontmatter with package bump types),
# bumps per-package versions, regenerates changelogs, commits, and tags.
#
# Usage:
#   ./scripts/release.sh                    # apply changesets
#   ./scripts/release.sh --dry-run          # preview without committing
#   ./scripts/release.sh --all <bump>       # bump all packages (patch|minor|major)
#
set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
WORKSPACE_TOML="$ROOT_DIR/Cargo.toml"
CHANGELOG="$ROOT_DIR/CHANGELOG.md"
CHANGESET_DIR="$ROOT_DIR/.changeset"

DRY_RUN=false
BUMP_ALL=""

# ── Package registry ─────────────────────────────────────────────────
# name:path pairs for all workspace crates
declare -A PKG_PATH=(
    [ds-parser]="compiler/ds-parser"
    [ds-analyzer]="compiler/ds-analyzer"
    [ds-codegen]="compiler/ds-codegen"
    [ds-layout]="compiler/ds-layout"
    [ds-types]="compiler/ds-types"
    [ds-incremental]="compiler/ds-incremental"
    [ds-cli]="compiler/ds-cli"
    [ds-physics]="engine/ds-physics"
    [ds-stream]="engine/ds-stream"
    [ds-stream-wasm]="engine/ds-stream-wasm"
)

# ── Parse flags ──────────────────────────────────────────────────────

while [[ $# -gt 0 ]]; do
    case "$1" in
        --dry-run) DRY_RUN=true; shift ;;
        --all)
            shift
            if [[ -z "${1:-}" ]]; then
                echo -e "${RED}Error:${NC} --all requires a bump type (patch|minor|major)"
                exit 1
            fi
            BUMP_ALL="$1"; shift
            ;;
        *) echo -e "${RED}Error:${NC} Unknown argument: $1"; exit 1 ;;
    esac
done

# ── Helpers ──────────────────────────────────────────────────────────

get_pkg_version() {
    local cargo_toml="$1/Cargo.toml"
    grep '^version' "$cargo_toml" | head -1 | sed 's/.*"\(.*\)".*/\1/'
}

bump_version() {
    local current="$1" bump="$2"
    local major minor patch
    IFS='.' read -r major minor patch <<< "$current"
    case "$bump" in
        major) echo "$((major + 1)).0.0" ;;
        minor) echo "$major.$((minor + 1)).0" ;;
        patch) echo "$major.$minor.$((patch + 1))" ;;
        *) echo "" ;;
    esac
}

set_pkg_version() {
    local cargo_toml="$1/Cargo.toml"
    local old_ver="$2" new_ver="$3"
    sed -i "0,/^version = \"$old_ver\"/s//version = \"$new_ver\"/" "$cargo_toml"
}

# ── Parse changesets ─────────────────────────────────────────────────
# Reads YAML frontmatter from .changeset/*.md files
# Format:
#   ---
#   "ds-parser": minor
#   "ds-codegen": patch
#   ---
#   Description of the change.

declare -A BUMPS        # package -> highest bump (major > minor > patch)
declare -a DESCRIPTIONS # changeset descriptions

BUMP_RANK_patch=1
BUMP_RANK_minor=2
BUMP_RANK_major=3

higher_bump() {
    local current="$1" candidate="$2"
    if [[ -z "$current" ]]; then echo "$candidate"; return; fi
    local cur_rank="BUMP_RANK_$current"
    local cand_rank="BUMP_RANK_$candidate"
    if [[ "${!cand_rank}" -gt "${!cur_rank}" ]]; then
        echo "$candidate"
    else
        echo "$current"
    fi
}

parse_changesets() {
    shopt -s nullglob
    local files=("$CHANGESET_DIR"/*.md)
    shopt -u nullglob

    for f in "${files[@]}"; do
        [[ "$(basename "$f")" == "README.md" ]] && continue

        local in_frontmatter=false
        local description=""

        while IFS= read -r line; do
            if [[ "$line" == "---" ]]; then
                if [[ "$in_frontmatter" == false ]]; then
                    in_frontmatter=true
                    continue
                else
                    in_frontmatter=false
                    continue
                fi
            fi

            if [[ "$in_frontmatter" == true ]]; then
                # Parse: "pkg-name": bump_type
                local pkg bump
                pkg=$(echo "$line" | sed -n 's/^"\([^"]*\)":.*/\1/p')
                bump=$(echo "$line" | sed -n 's/^"[^"]*": *\(.*\)/\1/p' | tr -d ' ')
                if [[ -n "$pkg" && -n "$bump" ]]; then
                    BUMPS[$pkg]=$(higher_bump "${BUMPS[$pkg]:-}" "$bump")
                fi
            else
                if [[ -n "$line" ]]; then
                    description+="$line"$'\n'
                fi
            fi
        done < "$f"

        if [[ -n "$description" ]]; then
            DESCRIPTIONS+=("$description")
        fi
    done
}

# ── Collect bumps ────────────────────────────────────────────────────

DESCRIPTIONS=()

if [[ -n "$BUMP_ALL" ]]; then
    for pkg in "${!PKG_PATH[@]}"; do
        BUMPS[$pkg]="$BUMP_ALL"
    done
    echo -e "${CYAN}Bumping all packages:${NC} $BUMP_ALL"
else
    parse_changesets
fi

if [[ ${#BUMPS[@]} -eq 0 ]]; then
    echo -e "${YELLOW}No changesets found.${NC} Nothing to release."
    echo "Add changesets in .changeset/ or use --all <bump>"
    exit 0
fi

# ── Preview ──────────────────────────────────────────────────────────

echo -e "${CYAN}DreamStack Release${NC}"
echo ""

for pkg in $(echo "${!BUMPS[@]}" | tr ' ' '\n' | sort); do
    local_path="${PKG_PATH[$pkg]:-}"
    if [[ -z "$local_path" ]]; then
        echo -e "  ${YELLOW}⚠${NC}  Unknown package: $pkg (skipping)"
        continue
    fi

    current=$(get_pkg_version "$ROOT_DIR/$local_path")
    new=$(bump_version "$current" "${BUMPS[$pkg]}")
    echo -e "  ${GREEN}$pkg${NC}  $current → $new  (${BUMPS[$pkg]})"
done

echo ""

if [[ ${#DESCRIPTIONS[@]} -gt 0 ]]; then
    echo -e "${CYAN}Changeset descriptions:${NC}"
    for desc in "${DESCRIPTIONS[@]}"; do
        echo "  - $(echo "$desc" | head -1)"
    done
    echo ""
fi

if [[ "$DRY_RUN" == true ]]; then
    echo -e "${YELLOW}[DRY RUN]${NC} No changes made."
    exit 0
fi

# ── Check prerequisites ─────────────────────────────────────────────

if ! command -v git-cliff &>/dev/null; then
    echo -e "${RED}Error:${NC} git-cliff not found. Install with: cargo install git-cliff"
    exit 1
fi

if [[ -n "$(git -C "$ROOT_DIR" status --porcelain)" ]]; then
    echo -e "${RED}Error:${NC} Working directory is not clean. Commit or stash changes first."
    exit 1
fi

# ── Step 1: Bump per-package versions ────────────────────────────────

echo -e "${GREEN}→${NC} Bumping package versions..."

HIGHEST_BUMP=""
for pkg in "${!BUMPS[@]}"; do
    local_path="${PKG_PATH[$pkg]:-}"
    [[ -z "$local_path" ]] && continue

    pkg_dir="$ROOT_DIR/$local_path"
    current=$(get_pkg_version "$pkg_dir")
    new=$(bump_version "$current" "${BUMPS[$pkg]}")

    if [[ -z "$new" ]]; then
        echo -e "  ${RED}✗${NC} $pkg — invalid bump '${BUMPS[$pkg]}'"
        continue
    fi

    set_pkg_version "$pkg_dir" "$current" "$new"
    echo -e "  ${GREEN}✓${NC} $pkg  $current → $new"

    HIGHEST_BUMP=$(higher_bump "$HIGHEST_BUMP" "${BUMPS[$pkg]}")
done

# ── Step 2: Bump workspace version ──────────────────────────────────

WORKSPACE_VERSION=$(grep -A2 '^\[workspace\.package\]' "$WORKSPACE_TOML" \
    | grep '^version' | sed 's/version = "\(.*\)"/\1/')
NEW_WORKSPACE=$(bump_version "$WORKSPACE_VERSION" "$HIGHEST_BUMP")

if [[ -n "$NEW_WORKSPACE" ]]; then
    sed -i "s/^version = \"$WORKSPACE_VERSION\"/version = \"$NEW_WORKSPACE\"/" "$WORKSPACE_TOML"
    echo -e "  ${GREEN}✓${NC} workspace  $WORKSPACE_VERSION → $NEW_WORKSPACE"
fi

VERSION_TAG="v${NEW_WORKSPACE:-$WORKSPACE_VERSION}"

# ── Step 3: Generate root changelog ─────────────────────────────────

echo -e "${GREEN}→${NC} Generating root changelog..."
git-cliff --config "$ROOT_DIR/cliff.toml" --tag "$VERSION_TAG" --output "$CHANGELOG"

# ── Step 4: Update per-package changelogs from changesets ─────────────
# Writes changeset descriptions into each affected crate's CHANGELOG.md
# under the new version heading. This avoids git-cliff's --include-path
# limitation where tag boundaries are lost if the tag commit doesn't
# touch the crate's files.

echo -e "${GREEN}→${NC} Updating per-package changelogs..."
updated=0

for pkg in "${!BUMPS[@]}"; do
    local_path="${PKG_PATH[$pkg]:-}"
    [[ -z "$local_path" ]] && continue

    pkg_dir="$ROOT_DIR/$local_path"
    pkg_changelog="$pkg_dir/CHANGELOG.md"
    new_ver=$(bump_version "$(get_pkg_version "$pkg_dir")" "${BUMPS[$pkg]}")
    today=$(date +%Y-%m-%d)

    # Collect descriptions from changesets that mention this package
    pkg_entries=""
    for desc in "${DESCRIPTIONS[@]}"; do
        pkg_entries+="- $desc"
    done

    # If --all with no changesets, add a generic entry
    if [[ -z "$pkg_entries" && -n "$BUMP_ALL" ]]; then
        pkg_entries="- Version bump"$'\n'
    fi

    # Build the new version section
    version_section="## [$new_ver] - $today"$'\n'$'\n'
    if [[ -n "$pkg_entries" ]]; then
        version_section+="### 🚀 Changes"$'\n'$'\n'
        version_section+="$pkg_entries"$'\n'
    fi

    if [[ -f "$pkg_changelog" ]]; then
        # Insert new version section after "## [Unreleased]" line
        TEMP_FILE=$(mktemp)
        awk -v section="$version_section" '
            /^## \[Unreleased\]/ {
                print
                print ""
                printf "%s", section
                next
            }
            { print }
        ' "$pkg_changelog" > "$TEMP_FILE"
        mv "$TEMP_FILE" "$pkg_changelog"
    else
        # Create changelog from scratch
        cat > "$pkg_changelog" << EOF
# Changelog

All notable changes to this package will be documented in this file.

## [Unreleased]

$version_section
EOF
    fi

    updated=$((updated + 1))
    echo -e "  ${GREEN}✓${NC} $pkg → $new_ver"
done
echo -e "  Updated ${GREEN}$updated${NC} package changelogs"

# ── Step 5: Clean up changeset files ────────────────────────────────

shopt -s nullglob
changeset_files=("$CHANGESET_DIR"/*.md)
shopt -u nullglob
cleaned=0

for f in "${changeset_files[@]}"; do
    [[ "$(basename "$f")" == "README.md" ]] && continue
    rm "$f"
    cleaned=$((cleaned + 1))
done

if [[ "$cleaned" -gt 0 ]]; then
    echo -e "${GREEN}→${NC} Cleaned up $cleaned changeset files."
fi

# ── Step 6: Commit and tag ──────────────────────────────────────────

echo -e "${GREEN}→${NC} Committing release..."
git -C "$ROOT_DIR" add -A
git -C "$ROOT_DIR" commit -m "chore(release): $VERSION_TAG"
git -C "$ROOT_DIR" tag -a "$VERSION_TAG" -m "Release $VERSION_TAG"

echo ""
echo -e "${GREEN}✓ Released $VERSION_TAG${NC}"
echo ""
echo -e "  Push with:  ${CYAN}git push && git push --tags${NC}"
