#!/usr/bin/env nu

# Taskserv/Component Discovery System
# Discovers available components (flat structure) and legacy taskservs (grouped structure).
# Post-migration: extensions/components/ is the primary source; extensions/taskservs/ is legacy.

use ../lib_provisioning/config/accessor.nu config-get
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval-soft]

# Resolve the components base path using all available signals.
def _components-path []: nothing -> string {
    let from_env = ($env.PROVISIONING_COMPONENTS_PATH? | default "")
    if ($from_env | is-not-empty) and ($from_env | path exists) { return $from_env }
    let prov = ($env.PROVISIONING? | default "")
    if ($prov | is-not-empty) {
        let derived = ($prov | path join "extensions" | path join "components")
        if ($derived | path exists) { return $derived }
    }
    config-get "paths.components" ""
}

# Discover all available taskservs/components.
# Searches components/ (flat, primary) then taskservs/ (grouped, legacy).
# Returns a unified list compatible with existing callers.
export def discover-taskservs [] {
    mut results = []

    # Primary: flat components/ directory (post-migration)
    let comp_path = (_components-path)
    if ($comp_path | is-not-empty) and ($comp_path | path exists) {
        let items = (do { ls $comp_path } | complete)
        if $items.exit_code == 0 {
            for item in ($items.stdout | where type == "dir") {
                let name       = ($item.name | path basename)
                let nickel_dir = ($item.name | path join "nickel")
                if not ($nickel_dir | path exists) { continue }
                $results = ($results | append {
                    name:        $name
                    type:        "component"
                    group:       ""
                    version:     ""
                    schema_path: $nickel_dir
                    main_schema: ""
                    dependencies: []
                    description: ""
                    available:   true
                    last_updated: $item.modified
                })
            }
        }
    }

    # Legacy: grouped taskservs/ directory (non-migrated workspaces)
    let ts_path_raw = (config-get "paths.taskservs" "")
    if ($ts_path_raw | is-not-empty) {
        let ts_path = ($ts_path_raw | path expand)
        if ($ts_path | path exists) and $ts_path != $comp_path {
            let items = (do { ls $ts_path } | complete)
            if $items.exit_code == 0 {
                for item in ($items.stdout | where type == "dir") {
                    let item_name  = ($item.name | path basename)
                    let schema_dir = ($item.name | path join "nickel")
                    let mod_path   = ($schema_dir | path join "nickel.mod")
                    # Group dir (has nickel/nickel.mod): scan applications inside
                    if ($mod_path | path exists) {
                        let subs = (do { ls $item.name } | complete)
                        if $subs.exit_code == 0 {
                            for sub in ($subs.stdout | where type == "dir" | where {|s|
                                let n = ($s.name | path basename)
                                $n != "nickel" and $n != "images"
                            }) {
                                let app_name = ($sub.name | path basename)
                                # Skip if already found in components/
                                if ($results | any {|r| $r.name == $app_name}) { continue }
                                $results = ($results | append {
                                    name:        $app_name
                                    type:        "taskserv"
                                    group:       $item_name
                                    version:     ""
                                    schema_path: $schema_dir
                                    main_schema: ""
                                    dependencies: []
                                    description: ""
                                    available:   true
                                    last_updated: $sub.modified
                                })
                            }
                        }
                    }
                }
            }
        }
    }

    $results | sort-by name
}

# Extract metadata from a taskserv's Nickel module (updated with group info)
def extract_taskserv_metadata [name: string, schema_path: string, group: string] {
    let mod_path = ($schema_path | path join "nickel.mod")

    # Try to parse TOML, skip if corrupted
    let toml_result = (do {
        open $mod_path | from toml
    } | complete)

    if $toml_result.exit_code != 0 {
        print $"⚠️  Skipping ($name): corrupted nickel.mod file"
        return null
    }

    let mod_content = $toml_result.stdout

    # Find Nickel schema files
    let schema_files = (glob ($schema_path | path join "*.ncl"))
    let main_schema = ($schema_files | where ($it | str contains $name) | first | default "")

    # Extract dependencies
    let dependencies = ($mod_content.dependencies? | default {} | columns)

    # Get description from schema file if available
    let description = if ($main_schema != "") {
        extract_schema_description $main_schema
    } else {
        ""
    }

    {
        name: $name
        type: "taskserv"
        group: $group
        version: $mod_content.package.version
        schema_path: $schema_path
        main_schema: $main_schema
        dependencies: $dependencies
        description: $description
        available: true
        last_updated: (ls $mod_path | get 0.modified)
    }
}

# Extract description from Nickel schema file
def extract_schema_description [schema_file: string] {
    if not ($schema_file | path exists) {
        return ""
    }

    # Read first few lines to find description
    let content = (open $schema_file | lines | take 10)
    let description_lines = ($content | where ($it | str starts-with "# ") | take 3)

    if ($description_lines | is-empty) {
        return ""
    }

    $description_lines
    | str replace "^# " ""
    | str join " "
    | str trim
}

# Search taskservs by name or description
export def search-taskservs [query: string] {
    discover-taskservs
    | where ($it.name | str contains $query) or ($it.description | str contains $query)
}

# Get specific taskserv info (updated to search both flat and grouped)
export def get-taskserv-info [name: string] {
    let taskservs = (discover-taskservs)
    let found = ($taskservs | where name == $name | first)

    if ($found | is-empty) {
        error make { msg: $"Taskserv '($name)' not found" }
    }

    $found
}

# List taskservs by group
export def list-taskservs-by-group [group: string] {
    discover-taskservs
    | where group == $group
}

# List all groups
export def list-taskserv-groups [] {
    discover-taskservs
    | get group
    | uniq
    | sort
}

# List taskservs by category/tag (legacy support)
export def list-taskservs-by-tag [tag: string] {
    discover-taskservs
    | where ($it.description | str contains $tag) or ($it.group | str contains $tag)
}

# Validate taskserv availability
export def validate-taskservs [names: list<string>] {
    let available = (discover-taskservs | get name)
    let missing = ($names | where ($it not-in $available))
    let found = ($names | where ($it in $available))

    {
        requested: $names
        found: $found
        missing: $missing
        valid: ($missing | is-empty)
    }
}

# Get the resolved directory for a taskserv or component by name.
# Returns the directory containing nickel/, taskserv/, etc.
# Prefers components/ (flat, post-migration) over taskservs/ (grouped, legacy).
export def get-taskserv-path [name: string]: nothing -> string {
    let info = get-taskserv-info $name

    # Component (flat structure) — base is already the directory
    if $info.type == "component" {
        let comp_base = (_components-path)
        return ($comp_base | path join $name)
    }

    # Legacy grouped taskserv
    let base_path = ($env.PROVISIONING? | default "" | path join "extensions/taskservs")
    if $info.group == "" or $info.group == "root" {
        $"($base_path)/($name)"
    } else {
        $"($base_path)/($info.group)/($name)"
    }
}

# Resolve the components base path from config (flat layout, no group dirs)
def components-base-path []: nothing -> string {
    let explicit = (do -i { config-get "paths.components" } | complete)
    if $explicit.exit_code == 0 {
        $explicit.stdout | str trim | path expand
    } else {
        let ts_path = (config-get "paths.taskservs" | path expand)
        $ts_path | path dirname | path join "components"
    }
}

# Discover all available components (flat structure: components/{name}/)
export def discover-components []: nothing -> list<record> {
    let base = (components-base-path)

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

    ls $base
    | where type == "dir"
    | each {|item|
        let name    = ($item.name | path basename)
        let meta_p  = ($item.name | path join "metadata.ncl")
        let ncl_p   = ($item.name | path join "nickel")
        let modes   = if ($meta_p | path exists) {
            ncl-eval-soft $meta_p [] [] | get -o modes | default ["taskserv"]
        } else { ["taskserv"] }
        let version = if ($meta_p | path exists) {
            ncl-eval-soft $meta_p [] "" | get -o version | default ""
        } else { "" }
        let description = if ($meta_p | path exists) {
            ncl-eval-soft $meta_p [] "" | get -o description | default ""
        } else { "" }
        {
            name:        $name
            type:        "component"
            modes:       $modes
            version:     $version
            description: $description
            path:        $item.name
            available:   ($ncl_p | path exists)
        }
    }
    | sort-by name
}

# Return the filesystem path for a named component
export def get-component-path [name: string]: nothing -> string {
    $"(components-base-path)/($name)"
}

# Return the first mode declared in a component's metadata.ncl
export def get-component-mode [name: string]: nothing -> string {
    let meta_p = (get-component-path $name | path join "metadata.ncl")
    if not ($meta_p | path exists) {
        error make { msg: $"metadata.ncl not found for component '($name)'" }
    }
    let parsed = (ncl-eval-soft $meta_p [] null)
    if ($parsed | is-empty) {
        error make { msg: $"Failed to parse metadata.ncl for component '($name)'" }
    }
    $parsed | get -o modes | default ["taskserv"] | first
}

# Search components by name or description substring
export def search-components [query: string]: nothing -> list<record> {
    discover-components
    | where ($it.name | str contains $query) or ($it.description | str contains $query)
}
