#!/usr/bin/env nu
# Agnostic Version Management Core
# No hardcoded tools or specific implementations

# use ../utils/error.nu *
# use ../utils/format.nu *

# Generic version record schema
export def version-schema [] {
    {
        id: ""              # Unique identifier
        type: ""            # Component type (tool/provider/taskserv/cluster)
        version: ""         # Current version
        fixed: false        # Version pinning
        source: {}          # Source configuration
        detector: {}        # Detection configuration
        updater: {}         # Update configuration
        metadata: {}        # Any additional data
    }
}

# Generic version operations interface
export def version-operations [] {
    {
        detect: { |config| "" }      # Detect installed version
        fetch: { |config| "" }       # Fetch available versions
        compare: { |v1, v2| 0 }      # Compare versions
        update: { |config, version| {} }  # Update to version
    }
}

# Version comparison (works with semantic and non-semantic versions)
export def compare-versions [
    v1: string
    v2: string
    --strategy: string = "semantic"  # semantic, string, numeric, custom
] {
    if $v1 == $v2 { return 0 }
    if ($v1 | is-empty) { return (-1) }
    if ($v2 | is-empty) { return 1 }

    match $strategy {
        "semantic" => {
            # Try semantic versioning - safely parse version parts
            let parts1 = ($v1 | split row "." | each { |p|
                let trimmed = ($p | str trim)
                if ($trimmed | is-empty) {
                    0
                } else {
                    # Attempt to convert to int, with error handling
                    if ($trimmed | parse -r "^[0-9]+$" | is-empty) {
                        0
                    } else {
                        $trimmed | into int
                    }
                }
            })
            let parts2 = ($v2 | split row "." | each { |p|
                let trimmed = ($p | str trim)
                if ($trimmed | is-empty) {
                    0
                } else {
                    # Attempt to convert to int, with error handling
                    if ($trimmed | parse -r "^[0-9]+$" | is-empty) {
                        0
                    } else {
                        $trimmed | into int
                    }
                }
            })

            let max_len = ([$parts1 $parts2] | each { |it| $it | length } | math max)

            for i in 0..<$max_len {
                let p1 = if ($i < ($parts1 | length)) { $parts1 | get $i } else { 0 }
                let p2 = if ($i < ($parts2 | length)) { $parts2 | get $i } else { 0 }

                if $p1 < $p2 { return (-1) }
                if $p1 > $p2 { return 1 }
            }
            0
        }
        "string" => {
            # Simple string comparison
            if $v1 < $v2 { (-1) } else if $v1 > $v2 { 1 } else { 0 }
        }
        "numeric" => {
            # Numeric comparison (for build numbers)
            let n1 = ($v1 | into float | default 0)
            let n2 = ($v2 | into float | default 0)
            if $n1 < $n2 { (-1) } else if $n1 > $n2 { 1 } else { 0 }
        }
        _ => 0
    }
}

# Execute command and extract version
export def detect-version [
    config: record  # Detection configuration
] {
    if ($config | is-empty) { return "" }

    let method = ($config | get method? | default "command")

    match $method {
        "command" => {
            let cmd = ($config | get command? | default "")
            if ($cmd | is-empty) { return "" }

            let result = (^sh -c $cmd err> /dev/null | complete)
            if $result.exit_code == 0 {
                let output = $result.stdout
                # Apply extraction pattern if provided
                if ($config | get pattern? | default null | is-not-empty) {
                    let parsed = ($output | parse -r $config.pattern)
                    if ($parsed | length) > 0 {
                        let row = ($parsed | get 0)
                        let capture_name = ($config | get capture? | default "capture0")
                        if ($capture_name in ($row | columns)) { $row | get $capture_name } else { "" }
                    } else {
                        ""
                    }
                } else {
                    $output | str trim
                }
            } else {
                ""
            }
        }
        "file" => {
            let path = ($config | get path? | default "")
            if not ($path | path exists) { return "" }

            let content = (open $path)
            if ($config | get field? | default null | is-not-empty) {
                $content | get $config.field? | default ""
            } else {
                $content | str trim
            }
        }
        "api" => {
            let url = ($config | get url? | default "")
            if ($url | is-empty) { return "" }

            let result = (http get $url --headers [User-Agent "provisionin-version-checker"] | complete)
            if $result.exit_code == 0 and ($result.stdout | length) > 0 {
                let response = ($result.stdout | from json)
                if ($config | get field? | default null | is-not-empty) {
                    $response | get $config.field? | default ""
                } else {
                    $response | to text | str trim
                }
            } else {
                ""
            }
        }
        "script" => {
            # Execute custom script
            let script = ($config | get script? | default "")
            if ($script | is-empty) { return "" }

            (nu -c $script | str trim | default "")
        }
        _ => ""
    }
}

# Fetch available versions from source
export def fetch-versions [
    config: record  # Source configuration
    --limit: int = 10
] {
    if ($config | is-empty) { return [] }

    let type = ($config | get type? | default "")

    match $type {
        "github" => {
            let repo = ($config | get repo? | default "")
            if ($repo | is-empty) { return [] }

            # Try releases first, then tags
            let endpoints = [
                $"https://api.github.com/repos/($repo)/releases"
                $"https://api.github.com/repos/($repo)/tags"
            ]

            for endpoint in $endpoints {
                let result = (http get $endpoint --headers [User-Agent "provisionin-version-checker"] | complete)
                if $result.exit_code == 0 and ($result.stdout | length) > 0 {
                    let response = ($result.stdout | from json | default [])
                    if ($response | length) > 0 {
                        return ($response
                            | first $limit
                            | each { |item|
                                let version = ($item | get tag_name? | default "")
                                $version | str replace -r '^v' ''
                            })
                    }
                }
            }
            []
        }
        "docker" => {
            let image = ($config | get image? | default "")
            if ($image | is-empty) { return [] }

            # Parse namespace/repo
            let parts = ($image | split row "/")
            let namespace = if ($parts | length) > 1 { $parts | get 0 } else { "library" }
            let repo = ($parts | last)

            let url = $"https://hub.docker.com/v2/namespaces/($namespace)/repositories/($repo)/tags"
            let result = (http get $url --headers [User-Agent "provisionin-version-checker"] | complete)
            if $result.exit_code == 0 and ($result.stdout | length) > 0 {
                let response = ($result.stdout | from json)
                if ($response | get results? | default null | is-not-empty) {
                    $response
                    | get results? | default null
                    | first $limit
                    | each { |tag| $tag.name }
                    | where { |v| $v !~ "latest|dev|nightly|edge|alpha|beta|rc" }
                } else {
                    []
                }
            } else {
                []
            }
        }
        "url" => {
            let url = ($config | get url? | default "")
            if ($url | is-empty) { return [] }

            let result = (http get $url --headers [User-Agent "provisionin-version-checker"] | complete)
            if $result.exit_code == 0 and ($result.stdout | length) > 0 {
                let response = ($result.stdout | from json)
                let field = ($config | get field? | default "")
                if ($field | is-not-empty) {
                    if ($field in ($response | columns)) { $response | get $field } else { [] }
                } else {
                    [$response | to text | str trim]
                }
            } else {
                []
            }
        }
        "script" => {
            let script = ($config | get script? | default "")
            if ($script | is-empty) { return [] }

            (nu -c $script | lines | default [])
        }
        _ => []
    }
}

# Generic version check
export def check-version [
    component: record
    --fetch-latest = false
    --respect-fixed = true
] {
    # Detect installed version
    let installed = if (($component | get detector? | default null) != null) {
        (detect-version $component.detector)
    } else { "" }

    # Get configured version
    let configured = ($component | get version? | default "")

    # Check if fixed
    let is_fixed = ($component | get fixed? | default false)

    # Fetch latest if requested
    let latest = if $fetch_latest and (not $is_fixed or not $respect_fixed) {
        if (($component | get source? | default null) != null) {
            let versions = (fetch-versions $component.source --limit=1)
            if ($versions | length) > 0 { $versions | get 0 } else { $configured }
        } else { $configured }
    } else { $configured }

    # Compare versions
    let comparison_strategy = ($component | get comparison? | default "semantic")

    let status = if $is_fixed and $respect_fixed {
        "fixed"
    } else if ($installed | is-empty) {
        "not_installed"
    } else if ($installed | is-not-empty) and ($latest != $installed) and ((compare-versions $installed $latest --strategy=$comparison_strategy) < 0) {
        "update_available"
    } else if (compare-versions $installed $configured --strategy=$comparison_strategy) < 0 {
        "behind_config"
    } else if (compare-versions $installed $configured --strategy=$comparison_strategy) > 0 {
        "ahead_config"
    } else {
        "up_to_date"
    }

    {
        id: $component.id
        type: $component.type
        installed: $installed
        configured: $configured
        latest: $latest
        fixed: $is_fixed
        status: $status
    }
}
