#!/usr/bin/env nu

# Nu Version Manager - Nushell implementation
# Usage: nu update_nu_versions.nu [list|ls|update|help]

# Function to get version from nushell Cargo.toml files
def get_nushell_version [cargo_file: path] {
    if ($cargo_file | path exists) {
        try {
            open --raw $cargo_file | lines | where $it =~ '^version = ' | first | parse 'version = "{version}"' | get version.0
        } catch { null }
    } else {
        null
    }
}

# Function to find version for a nu-* dependency in nushell crates
def get_target_version_for_dependency [dep_name: string, nushell_dir: string] {
    let target_path = ($nushell_dir | path join $"crates/($dep_name)/Cargo.toml")
    if ($target_path | path exists) {
        get_nushell_version $target_path
    } else {
        null
    }
}

# Function to extract the correct dependency name from a path
def get_correct_dep_name_from_path [line: string] {
    try {
        let path_match = ($line | parse --regex 'path = "([^"]*)"' | get capture0.0)
        # Extract the crate name from the path (last component after /crates/)
        $path_match | str replace --regex '.*/crates/' '' | str replace --regex '/.*' ''
    } catch {
        null
    }
}

# Function to fix dependency name conflicts
def fix_dependency_name_conflicts [content: list<string>] {
    mut updated_content = $content
    mut changes = []

    # Find lines with nu-* dependencies that have paths
    let conflicts = (
        $content
        | enumerate
        | where ($it.item | str contains "nu-") and ($it.item | str contains "path =")
        | each { |line|
            let declared_name = try {
                $line.item | parse --regex '^[[:space:]]*(nu-[a-zA-Z0-9_-]+)[[:space:]]*=' | get capture0.0
            } catch { null }

            let correct_name = get_correct_dep_name_from_path $line.item

            if ($declared_name | is-not-empty) and ($correct_name | is-not-empty) and $declared_name != $correct_name {
                {
                    index: $line.index,
                    declared_name: $declared_name,
                    correct_name: $correct_name,
                    line: $line.item
                }
            } else {
                null
            }
        }
        | where $it != null
    )

    for $conflict in $conflicts {
        let new_line = ($conflict.line | str replace $conflict.declared_name $conflict.correct_name)
        $updated_content = ($updated_content | update $conflict.index $new_line)
        $changes = ($changes | append $"⚠ Fixed dependency name conflict: ($conflict.declared_name) -> ($conflict.correct_name)")
    }

    { content: $updated_content, changes: $changes }
}

# Auto-detect versions from nushell source
let NUSHELL_DIR = if ("nushell" | path exists) {
    if ("nushell" | path type) == "symlink" {
        # Follow symlink to get actual directory
        "nushell" | path expand
    } else {
        "nushell"
    }
} else {
    error make { msg: "Nushell directory not found" }
}

# Get target versions from nushell source
let NU_PLUGIN_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-plugin/Cargo.toml")
let NU_PROTOCOL_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-protocol/Cargo.toml")
let NU_PLUGIN_TEST_VERSION = get_nushell_version ($NUSHELL_DIR | path join "crates/nu-plugin-test-support/Cargo.toml")

# Use nu-plugin version as the primary version (they should all be the same)
let NEW_VERSION = $NU_PLUGIN_VERSION

if ($NEW_VERSION | is-empty) {
    error make { msg: "Could not determine target version from nushell source. Make sure the nushell directory exists and contains the expected Cargo.toml files" }
}

# Function to get all unique versions currently used in plugin Cargo.toml files for nu-* dependencies
def get_current_versions [] {
    let plugin_dirs = get_plugin_dirs

    $plugin_dirs
    | each { |dir|
        let cargo_file = ($dir | path join "Cargo.toml")
        if ($cargo_file | path exists) {
            try {
                open --raw $cargo_file
                | lines
                | where $it =~ 'nu-[a-zA-Z0-9_-]+.*version = '
                | each { |line|
                    try {
                        $line | parse --regex 'version = "([^"]*)"' | get capture0.0
                    } catch { null }
                }
                | where $it != null
            } catch { [] }
        } else { [] }
    }
    | flatten
    | uniq
    | where $it != $NEW_VERSION
}

# Function to show usage information
def show_usage [] {
    print $"(ansi blue)Nu Version Manager(ansi reset)"
    print ""
    print "Usage: nu update_nu_versions.nu [command]"
    print ""
    print "Commands:"
    print $"  (ansi green)list, ls(ansi reset)    Show current versions in all Cargo.toml files"
    print $"  (ansi green)update(ansi reset)      Update versions to match nushell source ((ansi green)($NEW_VERSION)(ansi reset))"
    print $"  (ansi green)help, -h(ansi reset)    Show this help message"
    print ""
    print "If no command is specified, 'update' is assumed."
    print ""
}

# Function to extract version from a line
def extract_version [line: string] {
    let version_match = ($line | parse --regex 'version = "([^"]*)"')
    if ($version_match | length) > 0 {
        $version_match.0.capture0
    } else {
        null
    }
}

# Function to check if line has path dependency
def has_path_dependency [line: string] {
    $line | str contains "path ="
}

# Function to get plugin directories
def get_plugin_dirs [] {
    glob "nu_plugin_*" | where ($it | path type) == "dir" | sort
}

# Function to parse cargo.toml for version information
def parse_cargo_toml [cargo_file: path] {
    if not ($cargo_file | path exists) {
        return {
            package_version: null
            nu_plugin: null
            nu_protocol: null
            nu_json: null
            nu_plugin_test_support: null
            error: "Cargo.toml not found"
        }
    }

    let content = open --raw $cargo_file | lines

    # Get package version
    let package_version = try {
        $content
        | where $it =~ '^version = '
        | first
        | extract_version $in
    } catch { null }

    # Get nu-plugin version
    let nu_plugin_line = try {
        $content
        | where ($it =~ 'nu-plugin' and not ($it =~ 'nu-plugin-test-support'))
        | first
    } catch { null }

    let nu_plugin_info = if $nu_plugin_line != null {
        let version = extract_version $nu_plugin_line
        let has_path = has_path_dependency $nu_plugin_line
        {
            version: $version
            has_path: $has_path
            status: (if $version != null {
                if $has_path { "version_with_path" } else { "version_only" }
            } else {
                if $has_path { "path_only" } else { "not_found" }
            })
        }
    } else {
        { version: null, has_path: false, status: "not_found" }
    }

    # Get nu-protocol version
    let nu_protocol_line = try {
        $content
        | where $it =~ 'nu-protocol'
        | first
    } catch { null }

    let nu_protocol_info = if $nu_protocol_line != null {
        let version = extract_version $nu_protocol_line
        let has_path = has_path_dependency $nu_protocol_line
        {
            version: $version
            has_path: $has_path
            status: (if $version != null {
                if $has_path { "version_with_path" } else { "version_only" }
            } else {
                if $has_path { "path_only" } else { "not_found" }
            })
        }
    } else {
        { version: null, has_path: false, status: "not_found" }
    }

    # Get nu-json version
    let nu_json_line = try {
        $content
        | where $it =~ 'nu-json'
        | first
    } catch { null }

    let nu_json_info = if $nu_json_line != null {
        let version = extract_version $nu_json_line
        let has_path = has_path_dependency $nu_json_line
        {
            version: $version
            has_path: $has_path
            status: (if $version != null {
                if $has_path { "version_with_path" } else { "version_only" }
            } else {
                if $has_path { "path_only" } else { "not_found" }
            })
        }
    } else {
        { version: null, has_path: false, status: "not_found" }
    }

    # Get nu-plugin-test-support version
    let nu_test_line = try {
        $content
        | where $it =~ 'nu-plugin-test-support'
        | first
    } catch { null }

    let nu_test_info = if $nu_test_line != null {
        let version = extract_version $nu_test_line
        let has_path = has_path_dependency $nu_test_line
        {
            version: $version
            has_path: $has_path
            status: (if $version != null {
                if $has_path { "version_with_path" } else { "version_only" }
            } else {
                if $has_path { "path_only" } else { "not_found" }
            })
        }
    } else {
        { version: null, has_path: false, status: "not_found" }
    }

    {
        package_version: $package_version
        nu_plugin: $nu_plugin_info
        nu_protocol: $nu_protocol_info
        nu_json: $nu_json_info
        nu_plugin_test_support: $nu_test_info
        error: null
    }
}

# Function to format dependency info for display
def format_dependency [dep_info: record] {
    match $dep_info.status {
        "version_with_path" => ($dep_info.version + " " + (ansi yellow) + "(path)" + (ansi reset))
        "version_only" => $dep_info.version
        "path_only" => ((ansi yellow) + "path dependency only" + (ansi reset))
        "not_found" => ((ansi red) + "not found" + (ansi reset))
    }
}

# Function to list current versions
def list_versions [] {
    print $"(ansi blue)Nu Version Listing(ansi reset)"
    print "Current versions in all nu_plugin_* directories:"
    print ""

    let plugin_dirs = get_plugin_dirs

    if ($plugin_dirs | length) == 0 {
        print $"(ansi yellow)No nu_plugin_* directories found(ansi reset)"
        return
    }

    for $dir in $plugin_dirs {
        let plugin_name = ($dir | path basename)
        let cargo_file = ($dir | path join "Cargo.toml")

        print $"(ansi cyan)  ($plugin_name)(ansi reset):"

        let cargo_info = parse_cargo_toml $cargo_file

        if $cargo_info.error != null {
            print $"    (ansi red)Error: ($cargo_info.error)(ansi reset)"
            print ""
            continue
        }

        # Package version
        let pkg_version = if $cargo_info.package_version != null {
            $cargo_info.package_version
        } else {
            $"(ansi yellow)not specified(ansi reset)"
        }
        print $"    (ansi purple)Package(ansi reset): ($pkg_version)"

        # nu-plugin
        print $"    (ansi purple)nu-plugin(ansi reset): (format_dependency $cargo_info.nu_plugin)"

        # nu-protocol
        print $"    (ansi purple)nu-protocol(ansi reset): (format_dependency $cargo_info.nu_protocol)"

        # nu-json (only if found)
        if $cargo_info.nu_json.status != "not_found" {
            print $"    (ansi purple)nu-json(ansi reset): (format_dependency $cargo_info.nu_json)"
        }

        # nu-plugin-test-support (only if found)
        if $cargo_info.nu_plugin_test_support.status != "not_found" {
            print $"    (ansi purple)nu-plugin-test-support(ansi reset): (format_dependency $cargo_info.nu_plugin_test_support)"
        }

        print ""
    }
}

# Function to update a line with version replacement
def update_version_line [line: string, old_version: string, new_version: string] {
    $line | str replace $'version = "($old_version)"' $'version = "($new_version)"'
}

# Function to update a single cargo.toml file
def update_cargo_toml [cargo_file: path, nushell_dir: string] {
    if not ($cargo_file | path exists) {
        return { success: false, message: "Cargo.toml not found", changes: [] }
    }

    let content = open --raw $cargo_file | lines
    let backup_file = $"($cargo_file).backup"

    # Create backup
    $content | str join (char newline) | save -f --raw $backup_file

    # First, fix any dependency name conflicts
    let conflict_fix = fix_dependency_name_conflicts $content
    mut updated_content = $conflict_fix.content
    mut changes = $conflict_fix.changes

    # Find all nu-* dependencies in the file (use updated content after conflict fixes)
    # This handles two formats:
    # 1. Inline: nu-plugin = "0.108.0"
    # 2. Section header: [dev-dependencies.nu-plugin-test-support]
    let nu_dependencies = (
        (
            $updated_content
            | enumerate
            | where ($it.item =~ '^[[:space:]]*nu-[a-zA-Z0-9_-]+[[:space:]]*=') or ($it.item =~ '^\[dev-dependencies\.nu-[a-zA-Z0-9_-]+\]')
            | each { |line|
                if $line.item =~ '^\[dev-dependencies\.nu-[a-zA-Z0-9_-]+\]' {
                    # Section header format: [dev-dependencies.nu-plugin-test-support]
                    let dep_name = ($line.item | parse --regex '^\[dev-dependencies\.(nu-[a-zA-Z0-9_-]+)\]' | get capture0.0)
                    { index: $line.index, dep_name: $dep_name, line: $line.item, is_section: true }
                } else {
                    # Inline format: nu-plugin = "0.108.0"
                    let dep_name = ($line.item | parse --regex '^[[:space:]]*(nu-[a-zA-Z0-9_-]+)[[:space:]]*=' | get capture0.0)
                    { index: $line.index, dep_name: $dep_name, line: $line.item, is_section: false }
                }
            }
        )
        | group-by dep_name
        | transpose dep_name lines
        | each { |group| { dep_name: $group.dep_name, lines: $group.lines } }
    )

    for $dep_group in $nu_dependencies {
        let dep_name = $dep_group.dep_name
        let target_version = get_target_version_for_dependency $dep_name $nushell_dir

        if ($target_version | is-empty) {
            $changes = ($changes | append $"⚠ ($dep_name): No corresponding crate found in nushell source - skipping")
            continue
        }

        # Process each line for this dependency
        for $line_info in $dep_group.lines {
            let current_line = $line_info.line
            let line_index = $line_info.index

            # Check if this is a section header for a dev-dependency
            if ($line_info.is_section) {
                # For section headers like [dev-dependencies.nu-plugin-test-support]
                # we need to find and update the version line(s) in the following lines
                # Look at next few lines for version = "..."
                mut found_version = false
                mut search_idx = $line_index + 1

                while $search_idx < ($updated_content | length) and $search_idx < ($line_index + 5) {
                    let next_line = $updated_content | get $search_idx

                    # Stop if we hit another section header
                    if $next_line =~ '^\[' {
                        break
                    }

                    # Check if this line has version
                    if $next_line =~ 'version = ' {
                        let old_version = try {
                            $next_line | parse --regex 'version = "([^"]*)"' | get capture0.0
                        } catch { null }

                        if ($old_version | is-not-empty) and $old_version != $target_version {
                            let new_line = ($next_line | str replace $'version = "($old_version)"' $'version = "($target_version)"')
                            $updated_content = ($updated_content | update $search_idx $new_line)
                            $changes = ($changes | append $"✓ Updated ($dep_name) version from ($old_version) to ($target_version) in section header")
                            $found_version = true
                            break
                        } else if ($old_version | is-not-empty) and $old_version == $target_version {
                            $changes = ($changes | append $"→ ($dep_name): Already at target version ($target_version) in section")
                            $found_version = true
                            break
                        }
                    }

                    $search_idx = $search_idx + 1
                }

                if not $found_version {
                    $changes = ($changes | append $"⚠ ($dep_name): Could not find version line in section - skipping")
                }
            } else {
                # Inline dependency format
                # Check if it already has a path dependency
                if ($current_line | str contains "path =") {
                    # Update version but keep path
                    if ($current_line | str contains "version =") {
                        let old_version = try {
                            $current_line | parse --regex 'version = "([^"]*)"' | get capture0.0
                        } catch { null }

                        if ($old_version | is-not-empty) and $old_version != $target_version {
                            let new_line = ($current_line | str replace $'version = "($old_version)"' $'version = "($target_version)"')
                            $updated_content = ($updated_content | update $line_index $new_line)
                            $changes = ($changes | append $"✓ Updated ($dep_name) version from ($old_version) to ($target_version) path preserved")
                        } else if ($old_version | is-not-empty) and $old_version == $target_version {
                            $changes = ($changes | append $"→ ($dep_name): Already at target version ($target_version) with path")
                        }
                    } else {
                        # Add version to existing path dependency
                        let new_line = ($current_line | str replace "{" $'{ version = "($target_version)",')
                        $updated_content = ($updated_content | update $line_index $new_line)
                        $changes = ($changes | append $"✓ Added version ($target_version) to ($dep_name) path dependency")
                    }
                } else {
                    # No path dependency - add both version and path
                    let current_version = try {
                        $current_line | parse --regex 'version = "([^"]*)"' | get capture0.0
                    } catch { null }

                    if ($current_version | is-not-empty) {
                        # Replace version-only dependency with version + path
                        let relative_path = $"../nushell/crates/($dep_name)"
                        let new_line = ($current_line | str replace $'version = "($current_version)"' $'version = "($target_version)", path = "($relative_path)"')
                        $updated_content = ($updated_content | update $line_index $new_line)
                        $changes = ($changes | append $"✓ Updated ($dep_name) from ($current_version) to ($target_version) and added path dependency")
                    } else {
                        $changes = ($changes | append $"⚠ ($dep_name): Could not parse current version - skipping")
                    }
                }
            }
        }
    }

    # Save updated content if changes were made
    let actual_changes = ($changes | where not ($it | str starts-with "→") and not ($it | str starts-with "⚠"))
    if ($actual_changes | length) > 0 {
        $updated_content | str join (char newline) | save -f --raw $cargo_file
        {
            success: true
            message: $"Backup created: ($backup_file)"
            changes: $changes
        }
    } else {
        # Remove backup if no changes
        rm $backup_file
        {
            success: true
            message: "No version updates needed"
            changes: $changes
        }
    }
}

# Function to run the update process
def run_update [] {
    print $"(ansi blue)Nu Version Updater(ansi reset)"
    print $"Target version from nushell source: (ansi green)($NEW_VERSION)(ansi reset)"

    # Show detected current versions
    let current_versions = get_current_versions
    if ($current_versions | length) > 0 {
        print $"Current versions found in plugins: (ansi red)($current_versions | str join ', ')(ansi reset)"
    } else {
        print $"(ansi yellow)All plugins are already at target version(ansi reset)"
    }
    print ""

    let plugin_dirs = get_plugin_dirs

    if ($plugin_dirs | length) == 0 {
        print $"(ansi yellow)No nu_plugin_* directories found(ansi reset)"
        return
    }

    print $"(ansi blue)Found plugin directories:(ansi reset)"
    for $dir in $plugin_dirs {
        print $"  - ($dir)"
    }
    print ""

    # Process each plugin directory
    for $dir in $plugin_dirs {
        print $"(ansi blue)Processing ($dir)...(ansi reset)"

        let cargo_file = ($dir | path join "Cargo.toml")
        let result = update_cargo_toml $cargo_file $NUSHELL_DIR

        for $change in $result.changes {
            print $"    (ansi green)✓(ansi reset) ($change)"
        }

        if ($result.changes | length) > 0 {
            print $"    (ansi blue)($result.message)(ansi reset)"
        } else {
            print $"    (ansi yellow)($result.message)(ansi reset)"
        }

        print ""
    }

    print $"(ansi green)Version update process completed!(ansi reset)"
    print ""
    print ((ansi yellow) + "Note:" + (ansi reset) + " Backup files (.backup) have been created for modified files.")
    print ("You can restore them with: " + (ansi blue) + "mv file.backup file" + (ansi reset))
    print ""
    print $"(ansi yellow)Don't forget to:(ansi reset)"
    print "1. Test the plugins after the update"
    print "2. Update any documentation if needed"
    print "3. Commit the changes to version control"
}

# Main function
def main [command?: string] {
    # Ensure we're in the right directory (assume script is in the plugin directory)
    # cd (pwd)

    let cmd = $command | default "update"

    match $cmd {
        "list" | "ls" => { list_versions }
        "update" => { run_update }
        "help" | "-h" | "--help" => { show_usage }
        _ => {
            print $"(ansi red)Error: Unknown command '($cmd)'(ansi reset)"
            print ""
            show_usage
        }
    }
}

# Export functions for use
export def "nu-version list" [] { list_versions }
export def "nu-version ls" [] { list_versions }
export def "nu-version update" [] { run_update }
export def "nu-version help" [] { show_usage }

# Main entry point function
export def "nu-version" [command?: string] {
    main ($command | default "help")
}
