#!/usr/bin/env nu
# Dynamic configuration loader for version management
# Discovers and loads version configurations from the filesystem

# version/core star-import was dead — dropped (ADR-025 Phase 3 Layer 2).
use lib_provisioning/utils/nickel_processor.nu [ncl-eval ncl-eval-soft]

# Discover version configurations
export def discover-configurations [
    --base-path: string = ""
    --types: list = []  # Filter by types
] {
    let base = if ($base_path | is-empty) {
        ($env.PROVISIONING? | default $env.PWD)
    } else { $base_path }
    mut configurations = []

    # Load from known version files directly - try Nickel first, then YAML
    let version_files_nickel = [
        ($base | path join "core" | path join "versions.ncl")
    ]

    for file in $version_files_nickel {
        if ($file | path exists) {
            let configs = (load-nickel-version-file $file)
            if ($configs | is-not-empty) {
                $configurations = ($configurations | append $configs)
            }
        }
    }

    # Fallback to YAML for backward compatibility
    let version_files_yaml = [
        ($base | path join "versions.yaml")
        ($base | path join "core" | path join "versions.yaml")
    ]

    for file in $version_files_yaml {
        if ($file | path exists) {
            let configs = (load-configuration-file $file)
            if ($configs | is-not-empty) {
                $configurations = ($configurations | append $configs)
            }
        }
    }

    # Also check providers directory
    let providers_path = ($base | path join "extensions" | path join "providers")
    let providers_path_fallback = ($base | path join "providers")

    # Try extensions/providers first, then fall back to providers
    let active_providers_path = if ($providers_path | path exists) {
        $providers_path
    } else if ($providers_path_fallback | path exists) {
        $providers_path_fallback
    } else {
        ""
    }

    if ($active_providers_path | is-not-empty) {
        for provider_item in (ls $active_providers_path) {
            let provider_dir = ($active_providers_path | path join $provider_item.name)

            # Try Nickel version file first (single source of truth)
            let nickel_version_file = ($provider_dir | path join "nickel" | path join "version.ncl")
            if ($nickel_version_file | path exists) {
                let configs = (load-nickel-version-file $nickel_version_file)
                if ($configs | is-not-empty) {
                    $configurations = ($configurations | append $configs)
                }
            }

            # Fallback to YAML for backward compatibility
            let version_file = ($provider_dir | path join "versions.yaml")
            if ($version_file | path exists) {
                let configs = (load-configuration-file $version_file)
                if ($configs | is-not-empty) {
                    $configurations = ($configurations | append $configs)
                }
            }
        }
    }

    # Filter by types if specified
    if ($types | length) > 0 {
        $configurations | where type in $types
    } else {
        $configurations
    }
}

# Load configuration from file
export def load-configuration-file [
    file_path: string
] {
    if not ($file_path | path exists) { return [] }

    let ext = ($file_path | path parse | get extension)
    let parent_dir = ($file_path | path dirname)
    let context = (extract-context $parent_dir)

    mut configs = []

    match $ext {
        "yaml" | "yml" => {
            let data = (open $file_path)
            if ($data | describe | str contains "record") {
                # Convert record entries to configurations
                # Only process items that look like tool/component configs (have version, source, tags, detector, or check_cmd)
                for item in ($data | transpose key value) {
                    let value_desc = ($item.value | describe)
                    # Check if this is a valid configuration (record with tool-like fields)
                    # Must have version AND at least one of: source, tags, detector, check_cmd
                    let is_config = if ($value_desc | str contains "record") {
                        let has_version = (($item.value | get version? | default null) != null)
                        let has_source_or_tags = (
                            (($item.value | get source? | default null) != null) or
                            (($item.value | get tags? | default null) != null) or
                            (($item.value | get detector? | default null) != null) or
                            (($item.value | get check_cmd? | default null) != null)
                        )
                        $has_version and $has_source_or_tags
                    } else {
                        false
                    }

                    # Only append if it's a valid configuration, skip metadata-only entries
                    if $is_config {
                        let config = (create-configuration $item.key $item.value $context $file_path)
                        if ($config | is-not-empty) {
                            $configs = ($configs | append $config)
                        }
                    }
                }
            } else if ($data | describe | str contains "list") {
                # Already a list of configurations
                $configs = $data
            }
        }
        "k" => {
            # Parse Nickel files for version information
            let content = (open $file_path)
            let version_data = (extract-nickel-versions $content)
            for item in $version_data {
                let config = (create-configuration $item.name $item $context $file_path)
                $configs = ($configs | append $config)
            }
        }
        "toml" => {
            let data = (open $file_path)
            for section in ($data | transpose key value) {
                if (($section.value | get version? | default null) != null) {
                    let config = (create-configuration $section.key $section.value $context $file_path)
                    $configs = ($configs | append $config)
                }
            }
        }
        "json" => {
            let data = (open $file_path)
            if (($data | get components? | default null) != null) {
                $configs = $data.components
            } else {
                # Treat as single configuration
                $configs = [$data]
            }
        }
        _ => []
    }

    $configs
}

# Load Nickel version file by compiling it to JSON
export def load-nickel-version-file [
    file_path: string
] {
    if not ($file_path | path exists) { return [] }

    # Determine parent context - could be provider or core
    # provider: extensions/providers/{name}/nickel/version.ncl -> extensions/providers/{name}
    # core: core/versions.ncl -> core (no nickel dir)
    let parent_dir = if ($file_path | str contains "/nickel/version.ncl") {
        $file_path | path dirname | path dirname  # nickel/version.ncl -> provider_dir
    } else {
        $file_path | path dirname  # versions.ncl -> core
    }
    let context = (extract-context $parent_dir)

    mut configs = []

    # Compile Nickel to JSON (null on failure — return empty configs)
    let json_data = (ncl-eval-soft $file_path [] null)
    if ($json_data | is-empty) { return $configs }

    # Handle different Nickel output formats:
    # 1. Provider files: Single object with {name, version, dependencies}
    # 2. Core files: Object {core_versions: [{}]} or plain array [{}]
    let is_array = ($json_data | describe | str contains "^list")
    let items = (
        if ($json_data | get core_versions? | default null) != null {
            # Wrapped in core_versions key (core files)
            $json_data.core_versions
        } else if $is_array {
            # It's an array (plain array format)
            $json_data
        } else if ($json_data | get name? | default null) != null {
            # It's a single object (provider nickel/version.ncl)
            [$json_data]
        } else {
            []
        }
    )

    # Process each tool in items
    for item in $items {
        let tool_name = ($item | get name?)
        if ($tool_name | is-empty) { continue }

        let version_obj = ($item | get version? | default {})
        let current_version = ($version_obj | get current? | default "")
        let source = ($version_obj | get source? | default "")
        let tags = ($version_obj | get tags? | default "")
        let site = ($version_obj | get site? | default "")
        let detector_obj = ($item | get detector? | default {})

        # Transform to our configuration format
        let source_config = (if ($source | is-not-empty) {
            if ($source | str contains "github") {
                let repo_parse = ($source | parse -r 'github\.com/(?<repo>.+?)(/releases)?$')
                let repo = (if ($repo_parse | is-empty) { "" } else { $repo_parse | get 0.repo | default "" })
                { type: "github", repo: $repo }
            } else {
                { type: "url", url: $source }
            }
        } else if ($tags | is-not-empty) {
            if ($tags | str contains "github") {
                let repo_parse = ($tags | parse -r 'github\.com/(?<repo>.+?)(/tags)?$')
                let repo = (if ($repo_parse | is-empty) { "" } else { $repo_parse | get 0.repo | default "" })
                { type: "github", repo: $repo }
            } else {
                { type: "url", url: $tags }
            }
        } else {
            {}
        })

        let config = {
            id: $tool_name
            type: ($context.type? | default "unknown")
            category: ($context.category | default "")
            version: $current_version
            fixed: false
            source: $source_config
            detector: $detector_obj
            comparison: "semantic"
            metadata: {
                source_file: $file_path
                site: $site
                description: ""
                install_cmd: ""
                lib: ""
            }
        }

        if ($config | is-not-empty) {
            $configs = ($configs | append $config)
        }
    }

    $configs
}

# Extract context from path
export def extract-context [
    dir_path: string
] {
    let parts = ($dir_path | split row "/")

    # Determine type based on path structure
    let type = if ($parts | any { |p| $p == "providers" }) {
        "provider"
    } else if ($parts | any { |p| $p == "taskservs" }) {
        "taskserv"
    } else if ($parts | any { |p| $p == "clusters" }) {
        "cluster"
    } else if ($parts | any { |p| $p == "tools" }) {
        "tool"
    } else {
        "generic"
    }

    # Extract category/subcategory
    let category = if $type == "provider" {
        $parts | skip while { |p| $p != "providers" } | skip 1 | first
    } else if $type == "taskserv" {
        $parts | skip while { |p| $p != "taskservs" } | skip 1 | first
    } else {
        ""
    }

    {
        type: $type
        category: $category
        path: $dir_path
    }
}

# Create configuration object
export def create-configuration [
    id: string
    data: record
    context: record
    source_file: string
] {
    # Build detector configuration
    let detector = if (($data | get check_cmd? | default null) != null) {
        {
            method: "command"
            command: $data.check_cmd
            pattern: ($data | get parse_pattern? | default "")
            capture: ($data | get capture_group? | default "version")
        }
    } else if (($data | get detector? | default null) != null) {
        $data.detector
    } else {
        {}
    }

    # Build source configuration
    let source = if (($data | get source? | default null) != null) {
        if ($data.source | str contains "github.com") {
            let parsed = ($data.source | parse -r 'github\.com/(?<repo>.+)')
            let repo_str = if ($parsed | is-empty) { "" } else { $parsed | get 0 | get repo? | default "" }
            let repo_final = ($repo_str | str replace -r '/(releases|tags).*$' '')
            {
                type: "github"
                repo: $repo_final
            }
        } else if ($data.source | str starts-with "docker") {
            {
                type: "docker"
                image: ($data.source | str replace "docker://" "")
            }
        } else if ($data.source | str starts-with "http") {
            {
                type: "url"
                url: $data.source
                field: ($data | get version_field? | default "")
            }
        } else {
            { type: "custom", config: $data.source }
        }
    } else if (($data | get tags? | default null) != null) {
        # Infer from tags URL
        if ($data.tags | str contains "github") {
            let parsed = ($data.tags | parse -r 'github\.com/(?<repo>[^/]+/[^/]+)')
            let repo_str = if ($parsed | is-empty) { "" } else { $parsed | get 0 | get repo? | default "" }
            {
                type: "github"
                repo: $repo_str
            }
        } else {
            { type: "url", url: $data.tags }
        }
    } else {
        {}
    }

    # Build complete configuration
    {
        id: $id
        type: $context.type
        category: ($context.category | default "")
        version: ($data | get version? | default "")
        fixed: ($data | get fixed? | default false)
        source: $source
        detector: $detector
        comparison: ($data | get comparison? | default "semantic")
        metadata: {
            source_file: $source_file
            site: ($data | get site? | default "")
            description: ($data | get description? | default "")
            install_cmd: ($data | get install_cmd? | default "")
            lib: ($data | get lib? | default "")
        }
    }
}

# Extract version info from Nickel content
export def extract-nickel-versions [
    content: string
] {
    mut versions = []

    # Look for schema definitions with version fields
    let lines = ($content | lines)
    mut current_schema = ""
    mut current_data = {}

    for line in $lines {
        if ($line | str contains "schema ") {
            # New schema found
            if ($current_schema | is-not-empty) and (($current_data | get version? | default null) != null) {
                $versions = ($versions | append {
                    name: $current_schema
                    ...$current_data
                })
            }
            let parsed_schema = ($line | parse -r 'schema\s+(\w+)')
            $current_schema = if ($parsed_schema | is-empty) { "" } else { ($parsed_schema | get 0 | get capture0? | default "") }
            $current_data = {}
        } else if ($line | str contains "version:") or ($line | str contains "version =") {
            # Extract version
            let parsed_version = ($line | parse -r 'version[:\s=]+"?([^"]+)"?')
            let version = if ($parsed_version | is-empty) { "" } else { ($parsed_version | get 0 | get capture0? | default "") }
            if ($version | is-not-empty) {
                $current_data.version = $version
            }
        }
    }

    # Add last schema if valid
    if ($current_schema | is-not-empty) and (($current_data | get version? | default null) != null) {
        $versions = ($versions | append {
            name: $current_schema
            ...$current_data
        })
    }

    $versions
}
