# Nickel Module Loader Library
# Provides functions for discovering, syncing, and managing Nickel modules
# Used by CLI commands and other components
# Author: JesusPerezLorenzo
# Date: 2025-09-29

use config/accessor.nu *
use config/cache/simple-cache.nu *
use utils *

# Discover Nickel modules from extensions (providers, taskservs, clusters)
export def "discover-nickel-modules" [
    type: string  # "providers" | "taskservs" | "clusters"
] {
    # Fast path: don't load config, just use extensions path directly
    # This avoids Nickel evaluation which can hang the system
    let proj_root = ($env.PROVISIONING_ROOT? | default ($env.HOME | path join "project-provisioning"))
    let base_path = ($proj_root | path join "provisioning" "extensions" $type)

    if not ($base_path | path exists) {
        return []
    }

    # Discover modules using directory structure
    # Use proper Nushell ls with null stdin to avoid hanging
    let modules = (ls $base_path
        | where type == "dir"
        | get name
        | path basename)

    # Build table with Nickel information
    $modules | each {|module_name|
        let module_path = ($base_path | path join $module_name)
        let schema_path = ($module_path | path join "nickel")

        # Check if Nickel directory exists
        if not ($schema_path | path exists) {
            return null
        }

        # Read nickel.mod for metadata
        let mod_path = ($schema_path | path join "nickel.mod")
        let metadata = if ($mod_path | path exists) {
            parse-nickel-mod $mod_path
        } else {
            {name: "", version: "0.0.1", edition: "v0.11.3"}
        }

        # Determine Nickel module name based on type
        let module_name = match $type {
            "providers" => $"($module_name)_prov"
            "taskservs" => $"($module_name)_task"
            "clusters" => $"($module_name)_cluster"
            _ => $module_name
        }

        {
            name: $module_name
            type: $type
            path: $module_path
            schema_path: $schema_path
            module_name: $module_name
            version: $metadata.version
            edition: $metadata.edition
            has_nickel: true
        }
    } | compact
}

# Cached version of discover-nickel-modules
# NOTE: In practice, OS filesystem caching (dentry cache, inode cache) is more efficient
# than custom caching due to Nushell's JSON serialization overhead.
# This function is provided for future optimization when needed.
export def "discover-nickel-modules-cached" [
    type: string  # "providers" | "taskservs" | "clusters"
] {
    # Direct call - relies on OS filesystem cache for performance
    discover-nickel-modules $type
}

# Parse nickel.mod file and extract metadata
def "parse-nickel-mod" [
    mod_path: string
] {
    let content = (open $mod_path)

    # Simple TOML parsing for [package] section
    let lines = ($content | lines)
    mut name = ""
    mut version = "0.0.1"
    mut edition = "v0.11.3"

    for line in $lines {
        if ($line | str starts-with "name") {
            $name = ($line | parse "name = \"{name}\"" | get name.0? | default "")
        } else if ($line | str starts-with "version") {
            $version = ($line | parse "version = \"{version}\"" | get version.0? | default "0.0.1")
        } else if ($line | str starts-with "edition") {
            $edition = ($line | parse "edition = \"{edition}\"" | get edition.0? | default "v0.11.3")
        }
    }

    {name: $name, version: $version, edition: $edition}
}

# Sync Nickel dependencies for an infrastructure workspace
export def "sync-nickel-dependencies" [
    infra_path: string
    --manifest: string = "providers.manifest.yaml"
] {
    let manifest_path = ($infra_path | path join $manifest)

    if not ($manifest_path | path exists) {
        error make {msg: $"Manifest not found: ($manifest_path)"}
    }

    let manifest = (open $manifest_path)
    let modules_dir_name = (config-get "nickel.modules_dir" "nickel")
    let modules_dir = ($infra_path | path join $modules_dir_name)

    # Create modules directory if it doesn't exist
    mkdir $modules_dir

    _print $"🔄 Syncing Nickel dependencies for ($infra_path | path basename)..."

    # Sync each provider from manifest
    if ($manifest | get providers? | is-not-empty) {
        $manifest.providers | each {|provider|
            sync-provider-module $provider $modules_dir
        }
    }

    # Update nickel.mod
    update-nickel-mod $infra_path $manifest

    _print "✅ Nickel dependencies synced successfully"
}

# Sync a single provider module (create symlink)
def "sync-provider-module" [
    provider: record
    modules_dir: string
] {
    let discovered = (discover-nickel-modules-cached "providers"
        | where name == $provider.name)

    if ($discovered | is-empty) {
        error make {msg: $"Provider '($provider.name)' not found in extensions/providers"}
    }

    let module_info = ($discovered | first)
    let link_path = ($modules_dir | path join $module_info.module_name)

    # Remove existing symlink if present
    if ($link_path | path exists) {
        rm -f $link_path
    }

    # Create symlink (relative path for portability)
    let relative_path = (get-relative-path $modules_dir $module_info.schema_path)

    # Use ln -sf for symlink
    ^ln -sf $relative_path $link_path

    _print $"  ✓ ($provider.name) → ($link_path | path basename)"
}

# Get relative path from source to target
def "get-relative-path" [
    from: string
    to: string
] {
    # Calculate relative path
    # For now, use absolute path (Nickel handles this fine)
    $to
}

# Update nickel.mod with provider dependencies
export def "update-nickel-mod" [
    infra_path: string
    manifest: record
] {
    let mod_path = ($infra_path | path join "nickel.mod")

    if not ($mod_path | path exists) {
        error make {msg: $"nickel.mod not found at ($mod_path)"}
    }

    let current_mod = (open $mod_path)
    let modules_dir_name = (get-config | get nickel.modules_dir)

    # Generate provider dependencies
    let provider_deps = if ($manifest | get providers? | is-not-empty) {
        # Load all providers once to cache them
        let all_providers = (discover-nickel-modules-cached "providers")
        $manifest.providers | each {|provider|
            let discovered = ($all_providers | where name == $provider.name)
            if ($discovered | is-empty) {
                return ""
            }
            let module_info = ($discovered | first)
            $"($module_info.module_name) = { path = \"./($modules_dir_name)/($module_info.module_name)\", version = \"($provider.version)\" }"
        } | str join "\n"
    } else {
        ""
    }

    # Parse current nickel.mod and update dependencies section
    let lines = ($current_mod | lines)
    mut in_deps = false
    mut new_lines = []
    mut deps_section = []

    for line in $lines {
        if ($line | str trim | str starts-with "[dependencies]") {
            $in_deps = true
            $new_lines = ($new_lines | append $line)
            continue
        }

        if $in_deps {
            if ($line | str trim | str starts-with "[") {
                # End of dependencies section
                # Add provider deps before closing
                if ($provider_deps | str length) > 0 {
                    for dep_line in ($provider_deps | lines) {
                        $new_lines = ($new_lines | append $dep_line)
                    }
                }
                $in_deps = false
                $new_lines = ($new_lines | append $line)
            } else if (($line | str trim | str length) > 0) and not (($line | str contains "_prov") or ($line | str contains "_task") or ($line | str contains "_cluster")) {
                # Keep non-module-loader dependencies (like provisioning core)
                $new_lines = ($new_lines | append $line)
            }
        } else {
            $new_lines = ($new_lines | append $line)
        }
    }

    # If we're still in deps section at end, add provider deps
    if $in_deps and (($provider_deps | str length) > 0) {
        for dep_line in ($provider_deps | lines) {
            $new_lines = ($new_lines | append $dep_line)
        }
    }

    # Write updated nickel.mod
    $new_lines | str join "\n" | save -f $mod_path

    _print $"  ✓ Updated nickel.mod with provider dependencies"
}

# Install a provider to an infrastructure
export def "install-provider" [
    provider_name: string
    infra_path: string
    --version: string = "0.0.1"
] {
    # Discover provider using cached version
    let available = (discover-nickel-modules-cached "providers" | where name == $provider_name)

    if ($available | is-empty) {
        error make {msg: $"Provider '($provider_name)' not found"}
    }

    let provider_info = ($available | first)

    _print $"📦 Installing provider ($provider_name) for ($infra_path | path basename)..."

    # Update or create manifest
    update-manifest $infra_path $provider_name $version

    # Sync Nickel dependencies
    sync-nickel-dependencies $infra_path

    _print $"✅ Provider ($provider_name) installed successfully"
}

# Update providers.manifest.yaml with new provider
def "update-manifest" [
    infra_path: string
    provider_name: string
    version: string
] {
    let manifest_path = ($infra_path | path join "providers.manifest.yaml")

    let manifest = if ($manifest_path | path exists) {
        open $manifest_path
    } else {
        {providers: []}
    }

    # Check if provider already exists
    let existing_providers = ($manifest | get providers)
    let provider_exists = ($existing_providers | any {|p| $p.name == $provider_name})

    let updated_providers = if $provider_exists {
        # Update version
        $existing_providers | each {|p|
            if $p.name == $provider_name {
                {name: $provider_name, version: $version, enabled: true}
            } else {
                $p
            }
        }
    } else {
        # Add new provider
        $existing_providers | append {name: $provider_name, version: $version, enabled: true}
    }

    let updated_manifest = ($manifest | upsert providers $updated_providers)
    $updated_manifest | to yaml | save -f $manifest_path

    _print $"  ✓ Updated providers.manifest.yaml"
}

# Remove a provider from an infrastructure
export def "remove-provider" [
    provider_name: string
    infra_path: string
] {
    _print $"🗑️  Removing provider ($provider_name) from ($infra_path | path basename)..."

    let manifest_path = ($infra_path | path join "providers.manifest.yaml")

    if not ($manifest_path | path exists) {
        error make {msg: "providers.manifest.yaml not found"}
    }

    let manifest = (open $manifest_path)
    let updated_providers = ($manifest.providers | where name != $provider_name)
    let updated_manifest = ($manifest | upsert providers $updated_providers)

    $updated_manifest | to yaml | save -f $manifest_path

    # Remove symlink
    let modules_dir_name = (get-config | get nickel.modules_dir)
    let modules_dir = ($infra_path | path join $modules_dir_name)
    let discovered = (discover-nickel-modules-cached "providers" | where name == $provider_name)

    if not ($discovered | is-empty) {
        let module_info = ($discovered | first)
        let link_path = ($modules_dir | path join $module_info.module_name)

        if ($link_path | path exists) {
            rm -f $link_path
            _print $"  ✓ Removed symlink ($link_path | path basename)"
        }
    }

    # Sync to update nickel.mod
    sync-nickel-dependencies $infra_path

    _print $"✅ Provider ($provider_name) removed successfully"
}

# List all available Nickel modules
export def "list-nickel-modules" [
    type: string  # "providers" | "taskservs" | "clusters" | "all"
] {
    if $type == "all" {
        let providers = (discover-nickel-modules-cached "providers" | insert module_type "provider")
        let taskservs = (discover-nickel-modules-cached "taskservs" | insert module_type "taskserv")
        let clusters = (discover-nickel-modules-cached "clusters" | insert module_type "cluster")

        $providers | append $taskservs | append $clusters
    } else {
        discover-nickel-modules-cached $type | insert module_type $type
    }
}
