#!/usr/bin/env nu

# Release creation tool - creates GitHub releases with automated changelog
#
# Creates:
# - Git tags with proper versioning
# - GitHub releases with generated changelogs
# - Release notes from commit history
# - Asset uploads and management
# - Version bumping automation

use std log

def main [
    --version: string                           # Release version (e.g., 2.1.0, auto-increment if empty)
    --release-type: string = "patch"           # Release type: major, minor, patch, pre-release
    --pre-release = false                      # Mark as pre-release
    --draft                                   # Create as draft release
    --generate-changelog = true                # Generate changelog from commits
    --asset-dir: string = "packages"           # Directory containing release assets
    --dry-run = false                          # Show what would be created without doing it
    --push-tag = true                          # Push git tag to remote
    --auto-upload = true                       # Automatically upload assets
    --verbose                                   # Enable verbose logging
]
{

    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)

    # Determine release version
    let target_version = if $version == "" {
        determine_next_version $repo_root $release_type
    } else {
        $version
    }

    let release_config = {
        version: $target_version
        release_type: $release_type
        pre_release: $pre_release
        draft: $draft
        generate_changelog: $generate_changelog
        asset_dir: ($asset_dir | path expand)
        dry_run: $dry_run
        push_tag: $push_tag
        auto_upload: $auto_upload
        verbose: $verbose
        repo_root: $repo_root
    }

    log info $"Starting release creation with config: ($release_config)"

    # Validate repository state
    let repo_validation = validate_repository_state $repo_root $release_config

    if $repo_validation.status != "ready" {
        log error $"Repository not ready for release: ($repo_validation.reason)"
        exit 1
    }

    let release_results = []

    let result = (do {
        # Generate changelog if requested
        let changelog_result = if $release_config.generate_changelog {
            generate_changelog $repo_root $release_config
        } else {
            { status: "skipped", content: "", commits: [] }
        }

        let release_results = ($release_results | append { step: "changelog", result: $changelog_result })

        # Create git tag
        let tag_result = create_git_tag $repo_root $release_config

        let release_results = ($release_results | append { step: "git_tag", result: $tag_result })

        if $tag_result.status != "success" and not $release_config.dry_run {
            log error $"Failed to create git tag: ($tag_result.reason)"
            exit 1
        }

        # Push tag if requested
        let push_result = if $release_config.push_tag and not $release_config.dry_run {
            push_git_tag $repo_root $release_config
        } else {
            { status: "skipped", reason: "push disabled or dry run" }
        }

        let release_results = ($release_results | append { step: "push_tag", result: $push_result })

        # Create GitHub release
        let github_result = create_github_release $repo_root $release_config $changelog_result

        let release_results = ($release_results | append { step: "github_release", result: $github_result })

        if $github_result.status != "success" and not $release_config.dry_run {
            log error $"Failed to create GitHub release: ($github_result.reason)"
            exit 1
        }

        # Upload assets if requested
        let upload_result = if $release_config.auto_upload and not $release_config.dry_run {
            upload_release_assets $repo_root $release_config $github_result
        } else {
            { status: "skipped", reason: "auto-upload disabled or dry run", assets: [] }
        }

        let release_results = ($release_results | append { step: "upload_assets", result: $upload_result })

        # Update version in project files
        let version_update_result = update_project_version $repo_root $release_config

        let release_results = ($release_results | append { step: "version_update", result: $version_update_result })

        let summary = {
            version: $release_config.version
            release_type: $release_config.release_type
            success: true
            github_url: $github_result.release_url
            tag_name: $"v($release_config.version)"
            changelog_commits: ($changelog_result.commits | length)
            uploaded_assets: ($upload_result.assets | length)
            release_config: $release_config
            steps: $release_results
        }

        if $release_config.dry_run {
            log info $"Dry run completed - would create release v($release_config.version)"
        } else {
            log info $"Release v($release_config.version) created successfully: ($github_result.release_url)"
        }

        $summary
    } | complete)

    if $result.exit_code == 0 {
        return $result.stdout
    } else {
        log error $"Release creation failed: ($result.stderr)"

        # Cleanup on failure if not dry run
        if not $release_config.dry_run {
            cleanup_failed_release $repo_root $release_config
        }

        exit 1
    }
}

# Determine next version based on current version and release type
def determine_next_version [repo_root: string, release_type: string]
{
    cd $repo_root

    # Get latest tag
    let git_result = (do { git describe --tags --abbrev=0 2>/dev/null | str trim } | complete)
    let latest_tag = if $git_result.exit_code == 0 { $git_result.stdout } else { "v0.0.0" }

    # Parse version from tag (remove 'v' prefix if present)
    let current_version = ($latest_tag | str replace "^v" "")
    let version_parts = ($current_version | split row ".")

    if ($version_parts | length) < 3 {
        log warning $"Invalid version format: ($current_version), using 0.0.0"
        return "0.1.0"
    }

    let major = ($version_parts | get 0 | into int)
    let minor = ($version_parts | get 1 | into int)
    let patch = ($version_parts | get 2 | into int)

    match $release_type {
        "major" => { $"($major + 1).0.0" }
        "minor" => { $"($major).($minor + 1).0" }
        "patch" => { $"($major).($minor).($patch + 1)" }
        "pre-release" => { $"($major).($minor).($patch + 1)-rc1" }
        _ => {
            log warning $"Unknown release type: ($release_type), using patch"
            $"($major).($minor).($patch + 1)"
        }
    }
}

# Validate repository state before creating release
def validate_repository_state [repo_root: string, release_config: record]
{
    cd $repo_root

    # Check if we're in a git repository
    if not (".git" | path exists) {
        return { status: "failed", reason: "not in a git repository" }
    }

    # Check for uncommitted changes
    let status_result = (git status --porcelain | complete)
    if $status_result.exit_code == 0 and ($status_result.stdout | str trim) != "" {
        return { status: "failed", reason: "uncommitted changes in working directory" }
    }

    # Check if current branch is main/master
    let branch_result = (do { git branch --show-current | str trim } | complete)
    let current_branch = if $branch_result.exit_code == 0 { $branch_result.stdout } else { "unknown" }

    if not ($current_branch in ["main", "master"]) {
        return {
            status: "warning",
            reason: $"not on main branch (current: ($current_branch))",
            current_branch: $current_branch
        }
    }

    # Check if we can push to remote
    let remote_check = (do { git remote -v } | complete)

    if $remote_check.exit_code != 0 or ($remote_check.stdout | str trim) == "" {
        return { status: "warning", reason: "no remote repository configured" }
    }

    # Check if GitHub CLI is available (for GitHub releases)
    let gh_check = (do { gh --version } | complete)

    if $gh_check.exit_code != 0 {
        return { status: "warning", reason: "GitHub CLI (gh) not available - manual release creation required" }
    }

    return { status: "ready", current_branch: $current_branch }
}

# Generate changelog from commit history
def generate_changelog [repo_root: string, release_config: record]
{
    log info "Generating changelog from commit history..."

    cd $repo_root

    let changelog_ops = (do {
        # Get last release tag
        let tag_result = (do { git describe --tags --abbrev=0 2>/dev/null | str trim } | complete)
        let last_tag = if $tag_result.exit_code == 0 { $tag_result.stdout } else { "" }

        # Get commits since last release
        let git_log_cmd = if $last_tag != "" {
            $"git log ($last_tag)..HEAD --pretty=format:\"%h|%s|%an|%ad\" --date=short"
        } else {
            $"git log --pretty=format:\"%h|%s|%an|%ad\" --date=short"
        }

        let commit_lines = (bash -c $git_log_cmd | lines | where $it != "")

        # Parse commits
        let commits = $commit_lines | each {|line|
            let parts = ($line | split column "|" hash subject author date)
            if ($parts | length) >= 4 {
                {
                    hash: $parts.hash
                    subject: $parts.subject
                    author: $parts.author
                    date: $parts.date
                    type: (classify_commit_type $parts.subject)
                }
            } else {
                null
            }
        } | where $it != null

        # Group commits by type
        let grouped_commits = ($commits | group-by type)

        # Generate changelog content
        let changelog_content = format_changelog $grouped_commits $release_config

        {
            status: "success"
            content: $changelog_content
            commits: $commits
            commit_count: ($commits | length)
            since_tag: $last_tag
        }
    } | complete)

    if $changelog_ops.exit_code == 0 {
        $changelog_ops.stdout
    } else {
        {
            status: "failed"
            reason: $changelog_ops.stderr
            content: ""
            commits: []
        }
    }
}

# Classify commit type based on conventional commits
def classify_commit_type [subject: string]
{
    if ($subject =~ "^feat(\\(.+\\))?:") {
        "features"
    } else if ($subject =~ "^fix(\\(.+\\))?:") {
        "bug_fixes"
    } else if ($subject =~ "^docs(\\(.+\\))?:") {
        "documentation"
    } else if ($subject =~ "^refactor(\\(.+\\))?:") {
        "refactoring"
    } else if ($subject =~ "^test(\\(.+\\))?:") {
        "testing"
    } else if ($subject =~ "^chore(\\(.+\\))?:") {
        "maintenance"
    } else if ($subject =~ "^perf(\\(.+\\))?:") {
        "performance"
    } else if ($subject =~ "^ci(\\(.+\\))?:") {
        "ci_cd"
    } else {
        "other"
    }
}

# Format changelog content
def format_changelog [grouped_commits: record, release_config: record]
{
    let changelog = []

    $changelog = ($changelog | append $"# Release v($release_config.version)")
    $changelog = ($changelog | append "")
    $changelog = ($changelog | append $"**Release Date:** (date now | format date '%Y-%m-%d')")
    $changelog = ($changelog | append "")

    # Features
    if "features" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 🚀 New Features")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.features) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Bug Fixes
    if "bug_fixes" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 🐛 Bug Fixes")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.bug_fixes) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Performance Improvements
    if "performance" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## ⚡ Performance Improvements")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.performance) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Refactoring
    if "refactoring" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 🔧 Refactoring")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.refactoring) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Documentation
    if "documentation" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 📚 Documentation")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.documentation) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Maintenance
    if "maintenance" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 🧹 Maintenance")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.maintenance) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    # Other changes
    if "other" in ($grouped_commits | columns) {
        $changelog = ($changelog | append "## 📝 Other Changes")
        $changelog = ($changelog | append "")
        for commit in ($grouped_commits.other) {
            $changelog = ($changelog | append $"- ($commit.subject) (($commit.hash))")
        }
        $changelog = ($changelog | append "")
    }

    return ($changelog | str join "\n")
}

# Create git tag
def create_git_tag [repo_root: string, release_config: record]
{
    log info $"Creating git tag: v($release_config.version)"

    cd $repo_root

    if $release_config.dry_run {
        return {
            status: "success"
            tag_name: $"v($release_config.version)"
            dry_run: true
        }
    }

    let tag_ops = (do {
        let tag_name = $"v($release_config.version)"

        # Check if tag already exists
        let tag_check = (do { git tag -l $tag_name } | complete)

        if $tag_check.exit_code == 0 and ($tag_check.stdout | str trim) != "" {
            return {
                status: "failed"
                reason: $"tag ($tag_name) already exists"
                tag_name: $tag_name
            }
        }

        # Create annotated tag
        let tag_message = $"Release v($release_config.version)"
        git tag -a $tag_name -m $tag_message

        {
            status: "success"
            tag_name: $tag_name
            message: $tag_message
        }
    } | complete)

    if $tag_ops.exit_code == 0 {
        $tag_ops.stdout
    } else {
        {
            status: "failed"
            reason: $tag_ops.stderr
            tag_name: $"v($release_config.version)"
        }
    }
}

# Push git tag to remote
def push_git_tag [repo_root: string, release_config: record]
{
    log info $"Pushing git tag: v($release_config.version)"

    cd $repo_root

    let push_ops = (do {
        let tag_name = $"v($release_config.version)"
        git push origin $tag_name

        {
            status: "success"
            tag_name: $tag_name
            pushed_to: "origin"
        }
    } | complete)

    if $push_ops.exit_code == 0 {
        $push_ops.stdout
    } else {
        {
            status: "failed"
            reason: $push_ops.stderr
            tag_name: $"v($release_config.version)"
        }
    }
}

# Create GitHub release
def create_github_release [
    repo_root: string
    release_config: record
    changelog_result: record
]
{
    log info $"Creating GitHub release: v($release_config.version)"

    cd $repo_root

    if $release_config.dry_run {
        return {
            status: "success"
            release_url: $"https://github.com/owner/repo/releases/tag/v($release_config.version)"
            dry_run: true
        }
    }

    let release_ops = (do {
        let tag_name = $"v($release_config.version)"
        let release_title = $"Release v($release_config.version)"

        # Prepare release notes
        let release_notes = if $changelog_result.status == "success" {
            $changelog_result.content
        } else {
            $"Release v($release_config.version)\n\nChanges in this release:\n- Bug fixes and improvements"
        }

        # Save release notes to temporary file
        let notes_file = ($repo_root | path join "tmp-release-notes.md")
        $release_notes | save $notes_file

        # Build gh release create command
        let gh_cmd = ["gh", "release", "create", $tag_name, "--title", $release_title, "--notes-file", $notes_file]

        let gh_cmd = if $release_config.pre_release {
            $gh_cmd | append "--prerelease"
        } else {
            $gh_cmd
        }

        let gh_cmd = if $release_config.draft {
            $gh_cmd | append "--draft"
        } else {
            $gh_cmd
        }

        # Create release
        let release_result = (run-external --redirect-combine $gh_cmd.0 ...$gh_cmd.1.. | complete)

        # Clean up temporary file
        rm $notes_file

        if $release_result.exit_code == 0 {
            # Get release URL
            let release_url = ($release_result.stdout | str trim)

            {
                status: "success"
                release_url: $release_url
                tag_name: $tag_name
                title: $release_title
                pre_release: $release_config.pre_release
                draft: $release_config.draft
            }
        } else {
            {
                status: "failed"
                reason: $release_result.stderr
                tag_name: $tag_name
            }
        }
    } | complete)

    if $release_ops.exit_code == 0 {
        $release_ops.stdout
    } else {
        {
            status: "failed"
            reason: $release_ops.stderr
            tag_name: $"v($release_config.version)"
        }
    }
}

# Upload release assets
def upload_release_assets [
    repo_root: string
    release_config: record
    github_result: record
]
{
    log info "Uploading release assets..."

    if not ($release_config.asset_dir | path exists) {
        return {
            status: "skipped"
            reason: "asset directory does not exist"
            assets: []
        }
    }

    cd $repo_root

    let upload_ops = (do {
        # Find assets to upload
        let asset_patterns = ["*.tar.gz", "*.zip", "*.deb", "*.rpm", "*.msi", "*.dmg", "checksums.txt", "manifest.json"]
        let assets = []

        for pattern in $asset_patterns {
            let found_assets = (find $release_config.asset_dir -name $pattern -type f)
            $assets = ($assets | append $found_assets)
        }

        $assets = ($assets | uniq)

        if ($assets | length) == 0 {
            return {
                status: "skipped"
                reason: "no assets found to upload"
                assets: []
            }
        }

        let tag_name = $"v($release_config.version)"
        let upload_results = []

        # Upload each asset
        for asset in $assets {
            let asset_upload = (do {
                log info $"Uploading asset: ($asset | path basename)"
                let upload_result = (gh release upload $tag_name $asset | complete)

                if $upload_result.exit_code == 0 {
                    {
                        asset: ($asset | path basename)
                        status: "success"
                        size: (ls $asset | get 0.size)
                    }
                } else {
                    {
                        asset: ($asset | path basename)
                        status: "failed"
                        reason: $upload_result.stderr
                    }
                }
            } | complete)

            if $asset_upload.exit_code == 0 {
                $upload_results = ($upload_results | append $asset_upload.stdout)
            } else {
                $upload_results = ($upload_results | append {
                    asset: ($asset | path basename)
                    status: "failed"
                    reason: $asset_upload.stderr
                })
            }
        }

        let successful_uploads = ($upload_results | where status == "success" | length)
        let total_assets = ($upload_results | length)

        {
            status: (if $successful_uploads == $total_assets { "success" } else { "partial" })
            total_assets: $total_assets
            successful_uploads: $successful_uploads
            failed_uploads: ($total_assets - $successful_uploads)
            assets: $upload_results
        }
    } | complete)

    if $upload_ops.exit_code == 0 {
        $upload_ops.stdout
    } else {
        {
            status: "failed"
            reason: $upload_ops.stderr
            assets: []
        }
    }
}

# Update project version in files
def update_project_version [repo_root: string, release_config: record]
{
    log info "Updating project version files..."

    if $release_config.dry_run {
        return { status: "skipped", reason: "dry run", updated_files: [] }
    }

    cd $repo_root

    let updated_files = []

    # Update Cargo.toml files
    let cargo_files = (find . -name "Cargo.toml" -type f)
    for cargo_file in $cargo_files {
        let cargo_update = (do {
            let content = (open $cargo_file --raw)
            let updated_content = ($content | str replace 'version = "[^"]*"' $'version = "($release_config.version)"')

            if $content != $updated_content {
                $updated_content | save $cargo_file
                $updated_files = ($updated_files | append $cargo_file)
                log info $"Updated version in ($cargo_file)"
            }
        } | complete)

        if $cargo_update.exit_code != 0 {
            log warning $"Failed to update version in ($cargo_file)"
        }
    }

    # Update package.json files
    let package_files = (find . -name "package.json" -type f)
    for package_file in $package_files {
        let package_update = (do {
            let package_data = (open $package_file)
            let updated_data = ($package_data | upsert version $release_config.version)
            $updated_data | to json | save $package_file
            $updated_files = ($updated_files | append $package_file)
            log info $"Updated version in ($package_file)"
        } | complete)

        if $package_update.exit_code != 0 {
            log warning $"Failed to update version in ($package_file)"
        }
    }

    {
        status: "success"
        updated_files: $updated_files
        files_updated: ($updated_files | length)
    }
}

# Cleanup failed release
def cleanup_failed_release [repo_root: string, release_config: record] {
    log info "Cleaning up failed release..."

    cd $repo_root

    # Delete local tag if it was created
    let tag_name = $"v($release_config.version)"
    let local_delete = (do { git tag -d $tag_name } | complete)
    if $local_delete.exit_code == 0 {
        log info $"Deleted local tag: ($tag_name)"
    }

    # Delete remote tag if it was pushed
    if $release_config.push_tag {
        let remote_delete = (do { git push --delete origin $tag_name } | complete)
        if $remote_delete.exit_code == 0 {
            log info $"Deleted remote tag: ($tag_name)"
        }
    }
}

# Show release information
def "main info" [] {
    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)
    cd $repo_root

    let tag_result = (do { git describe --tags --abbrev=0 2>/dev/null | str trim } | complete)
    let latest_tag = if $tag_result.exit_code == 0 { $tag_result.stdout } else { "none" }

    let branch_result = (do { git branch --show-current | str trim } | complete)
    let current_branch = if $branch_result.exit_code == 0 { $branch_result.stdout } else { "unknown" }

    let commits_since_tag = if $latest_tag != "none" {
        let count_result = (do { git rev-list $"($latest_tag)..HEAD" --count | into int } | complete)
        if $count_result.exit_code == 0 { $count_result.stdout } else { 0 }
    } else {
        let count_result = (do { git rev-list HEAD --count | into int } | complete)
        if $count_result.exit_code == 0 { $count_result.stdout } else { 0 }
    }

    {
        repository: $repo_root
        current_branch: $current_branch
        latest_tag: $latest_tag
        commits_since_tag: $commits_since_tag
        next_versions: {
            patch: (determine_next_version $repo_root "patch")
            minor: (determine_next_version $repo_root "minor")
            major: (determine_next_version $repo_root "major")
        }
        github_cli_available: ((do { gh --version } | complete).exit_code == 0)
    }
}

# Preview changelog for upcoming release
def "main preview" [
    --version: string = ""                      # Version for preview
    --release-type: string = "patch"           # Release type for version calculation
] {
    let repo_root = ($env.PWD | path dirname | path dirname | path dirname)

    let target_version = if $version == "" {
        determine_next_version $repo_root $release_type
    } else {
        $version
    }

    let preview_config = { version: $target_version, generate_changelog: true }
    let changelog_result = generate_changelog $repo_root $preview_config

    print $"Preview of changelog for v($target_version):"
    print "=" * 50
    print $changelog_result.content
}
