#!/usr/bin/env nu
# Component management module — list, show, status for extensions/components.
#
# Two perspectives per component:
#   extension — what exists in extensions/components/{name}/ (metadata, modes, contract)
#   workspace — how it's instantiated in infra/{ws}/components/{name}.ncl
#
# Ontology data (FSM state, edges) is read via ontoref when available (defensive).

use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval-soft, default-ncl-paths]

# Resolve the extensions/components/ base path.
def _comp-ext-base []: 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 p = ($prov | path join "extensions" | path join "components")
        if ($p | path exists) { return $p }
    }
    ""
}

# Resolve the workspace root for a given workspace name.
# Checks PROVISIONING_KLOUD_PATH env, then walks known workspace directories.
def _ws-root [workspace: string]: nothing -> string {
    if ($workspace | is-empty) { return "" }
    let from_env = ($env.PROVISIONING_KLOUD_PATH? | default "")
    if ($from_env | is-not-empty) and ($from_env | path basename) == $workspace {
        return $from_env
    }
    let prov = ($env.PROVISIONING? | default "")
    if ($prov | is-not-empty) {
        let ws_root = ($prov | path dirname | path join "workspaces" | path join $workspace)
        if ($ws_root | path exists) { return $ws_root }
    }
    ""
}

# Export a Nickel file to a record. Returns null on failure.
# Uses default-ncl-paths to match the daemon's cache key derivation.
def _ncl-export [file_path: string]: nothing -> any {
    let ws_root = ($file_path | path dirname | path dirname | path dirname)
    ncl-eval-soft $file_path (default-ncl-paths $ws_root) null
}

# Read FSM dimension for a component from state.ncl via ontoref or raw NCL export.
def _read-fsm-state [name: string, ws_root: string]: nothing -> record {
    let dim_id = $"($name)-status"
    # Try ontoref first (richer output)
    let onto_result = (do {
        ^ontoref describe state $dim_id --fmt json --workspace $ws_root
    } | complete)
    if $onto_result.exit_code == 0 {
        let parsed = (do { $onto_result.stdout | from json } | complete)
        if $parsed.exit_code == 0 { return $parsed.stdout }
    }
    # Fallback: export state.ncl and filter
    let state_path = ($ws_root | path join ".ontology" | path join "state.ncl")
    if not ($state_path | path exists) { return {} }
    let prov = ($env.PROVISIONING? | default "")
    let state_data = (ncl-eval-soft $state_path (default-ncl-paths $ws_root) {})
    if ($state_data | is-empty) { return {} }
    let dims = ($state_data | get -o dimensions | default [])
    $dims | where {|d| ($d | get -o id | default "") == $dim_id } | get 0? | default {}
}

# Read ontology node and edges for a component from core.ncl.
def _read-onto-node [name: string, ws_root: string]: nothing -> record {
    let core_path = ($ws_root | path join ".ontology" | path join "core.ncl")
    if not ($core_path | path exists) { return { node: null, edges_from: [], edges_to: [] } }
    let prov = ($env.PROVISIONING? | default "")
    let data = (ncl-eval-soft $core_path (default-ncl-paths $ws_root) null)
    if $data == null { return { node: null, edges_from: [], edges_to: [] } }
    let nodes = ($data | get -o nodes | default [])
    let edges = ($data | get -o edges | default [])
    let node  = ($nodes | where {|n| ($n | get -o id | default "") == $name } | get 0? | default null)
    let edges_from = ($edges | where {|e| ($e | get -o from | default "") == $name })
    let edges_to   = ($edges | where {|e| ($e | get -o to | default "") == $name })
    { node: $node, edges_from: $edges_from, edges_to: $edges_to }
}

# List all components from extensions/components/ with optional mode filter and workspace state.
export def component-list [mode: string, workspace: string]: nothing -> nothing {
    let base = (_comp-ext-base)
    if ($base | is-empty) or not ($base | path exists) {
        print "❌ extensions/components/ not found. Set PROVISIONING env var."
        return
    }

    let ws_root = (_ws-root $workspace)
    let show_state = ($ws_root | is-not-empty)

    mut rows = []
    for item in (ls $base | where type == "dir") {
        let name     = ($item.name | path basename)
        let meta_p   = ($item.name | path join "metadata.ncl")
        let meta     = if ($meta_p | path exists) { _ncl-export $meta_p } else { null }
        let modes    = if $meta != null { $meta | get -o modes | default ["taskserv"] } else { ["taskserv"] }
        let version  = if $meta != null { $meta | get -o version | default "" } else { "" }
        let desc     = if $meta != null { $meta | get -o description | default "" } else { "" }

        # Mode filter
        if ($mode | is-not-empty) and ($mode not-in $modes) { continue }

        let state = if $show_state {
            let dim = (_read-fsm-state $name $ws_root)
            if ($dim | is-empty) { "—" } else {
                let cur = ($dim | get -o current_state | default "—")
                let des = ($dim | get -o desired_state | default "")
                if ($des | is-not-empty) and $cur != $des { $"($cur) → ($des)" } else { $cur }
            }
        } else { "—" }

        $rows = ($rows | append {
            name:    $name
            mode:    ($modes | str join "·")
            state:   $state
            version: $version
        })
    }

    if ($rows | is-empty) {
        print "No components found."
        return
    }

    let header = if $show_state { $"Components  [workspace: ($workspace)]" } else { "Components  [extension catalog]" }
    print $header
    print "────────────────────────────────────────────────────────────"
    $rows | table
}

# Show full details for a named component.
export def component-show [name: string, workspace: string, ext_only: bool]: nothing -> nothing {
    let base = (_comp-ext-base)
    let ext_dir = ($base | path join $name)
    if not ($ext_dir | path exists) {
        print $"❌ Component '($name)' not found in extensions/components/"
        return
    }

    let meta_p = ($ext_dir | path join "metadata.ncl")
    let meta   = if ($meta_p | path exists) { _ncl-export $meta_p } else { null }

    # Extension section
    let modes   = if $meta != null { $meta | get -o modes | default ["taskserv"] } else { ["taskserv"] }
    let version = if $meta != null { $meta | get -o version | default "" } else { "" }
    let desc    = if $meta != null { $meta | get -o description | default "" } else { "" }
    let tags    = if $meta != null { $meta | get -o tags | default [] | str join " · " } else { "" }

    # Defaults (requires/provides/operations from nickel/defaults.ncl)
    let defaults_p = ($ext_dir | path join "nickel" | path join "defaults.ncl")
    let defaults   = if ($defaults_p | path exists) { _ncl-export $defaults_p } else { null }
    let def_rec    = if $defaults != null { $defaults | get -o $name | default {} } else { {} }
    let requires   = ($def_rec | get -o requires | default {})
    let provides   = ($def_rec | get -o provides | default {})
    let operations = ($def_rec | get -o operations | default {})

    print $"┌─ ($name | str upcase) ─────────────────────────────────"
    print $"│ ($desc)"
    print $"├────────────────────────────────────────────────────────"
    let modes_str = ($modes | str join " · ")
    print $"│ VERSION    ($version)"
    print $"│ MODES      ($modes_str)"
    if ($tags | is-not-empty) { print $"│ TAGS       ($tags)" }

    # REQUIRES
    let req_storage = ($requires | get -o storage | default null)
    let req_ports   = ($requires | get -o ports | default [])
    let req_creds   = ($requires | get -o credentials | default [])
    if $req_storage != null or ($req_ports | is-not-empty) or ($req_creds | is-not-empty) {
        print "├─── REQUIRES ───────────────────────────────────────────"
        if $req_storage != null {
            let persist_label = if ($req_storage.persistent? | default false) { "persistent" } else { "ephemeral" }
            let stor_size = ($req_storage.size? | default "?")
            print $"│ storage    ($stor_size) ($persist_label)"
        }
        for p in $req_ports {
            let pport    = ($p.port? | default 0 | into string)
            let pproto   = ($p.protocol? | default "TCP")
            let pexpose  = ($p.exposure? | default "internal")
            print $"│ port       ($pport)/($pproto) \(($pexpose)\)"
        }
        if ($req_creds | is-not-empty) {
            let creds_str = ($req_creds | str join " · ")
            print $"│ creds      ($creds_str)"
        }
    }

    # PROVIDES
    let prov_svc  = ($provides | get -o service | default "")
    let prov_port = ($provides | get -o port | default null)
    let prov_dbs  = ($provides | get -o databases | default [])
    if ($prov_svc | is-not-empty) or $prov_port != null or ($prov_dbs | is-not-empty) {
        print "├─── PROVIDES ───────────────────────────────────────────"
        if ($prov_svc | is-not-empty) and $prov_port != null {
            print $"│ service    ($prov_svc):($prov_port)"
        } else if ($prov_svc | is-not-empty) {
            print $"│ service    ($prov_svc)"
        }
        if ($prov_dbs | is-not-empty) {
            let dbs_str = ($prov_dbs | str join " · ")
            print $"│ databases  ($dbs_str)"
        }
    }

    # OPERATIONS
    let ops_enabled = ($operations | transpose k v | where v == true | each {|r| $r.k })
    if ($ops_enabled | is-not-empty) {
        let ops_str = ($ops_enabled | str join " · ")
        print "├─── OPERATIONS ─────────────────────────────────────────"
        print $"│ ($ops_str)"
    }

    if not $ext_only and ($workspace | is-not-empty) {
        let ws_root = (_ws-root $workspace)
        if ($ws_root | is-not-empty) {
            # Workspace instance
            let comp_p = ($ws_root | path join "infra" | path join $workspace | path join "components" | path join $"($name).ncl")
            let comp_data = if ($comp_p | path exists) { _ncl-export $comp_p } else { null }
            let inst     = if $comp_data != null { $comp_data | get -o $name | default {} } else { {} }
            let inst_mode = ($inst | get -o mode | default "")
            let inst_ns   = ($inst | get -o namespace | default "")
            let inst_tgt  = ($inst | get -o target | default "")

            print "├─── WORKSPACE INSTANCE ─────────────────────────────────"
            if ($inst_mode | is-not-empty) { print $"│ mode       ($inst_mode)" }
            if ($inst_ns | is-not-empty)   { print $"│ namespace  ($inst_ns)" }
            if ($inst_tgt | is-not-empty)  { print $"│ target     ($inst_tgt)" }

            # FSM state
            let dim = (_read-fsm-state $name $ws_root)
            if not ($dim | is-empty) {
                let cur = ($dim | get -o current_state | default "—")
                let des = ($dim | get -o desired_state | default "")
                let blk = ($dim | get -o transitions | default [] | get 0? | default {} | get -o blocker | default "")
                let blk_short = ($blk | str substring 0..80)
                print "├─── STATE ───────────────────────────────────────────"
                print $"│ current    ($cur)"
                if ($des | is-not-empty) { print $"│ desired    ($des)" }
                if ($blk | is-not-empty) { print $"│ blocker    ($blk_short)" }
            }

            # Ontology
            let onto = (_read-onto-node $name $ws_root)
            if $onto.node != null {
                let node      = $onto.node
                let node_lvl  = ($node.level? | default "?")
                let node_pole = ($node.pole? | default "?")
                print "├─── ONTOLOGY ────────────────────────────────────────"
                print $"│ node       ($name) \(($node_lvl) / ($node_pole)\)"
                let arts = ($node | get -o artifact_paths | default [])
                if ($arts | is-not-empty) {
                    let arts_str = ($arts | str join " · ")
                    print $"│ artifacts  ($arts_str)"
                }
                let adrs = ($node | get -o adrs | default [])
                if ($adrs | is-not-empty) {
                    let adrs_str = ($adrs | str join " · ")
                    print $"│ adrs       ($adrs_str)"
                }
                if ($onto.edges_from | is-not-empty) {
                    let consumers = ($onto.edges_from | each {|e|
                        let eto   = ($e | get -o to   | default "?")
                        let ekind = ($e | get -o kind | default "")
                        $"($eto) \(($ekind)\)"
                    } | str join " · ")
                    print $"│ used-by    ($consumers)"
                }
                if ($onto.edges_to | is-not-empty) {
                    let uses = ($onto.edges_to | each {|e|
                        let efrom = ($e | get -o from | default "?")
                        let ekind = ($e | get -o kind | default "")
                        $"($efrom) \(($ekind)\)"
                    } | str join " · ")
                    print $"│ uses       ($uses)"
                }
            }
        }
    }

    print "└────────────────────────────────────────────────────────"
}

# Show only FSM state for a component.
export def component-status [name: string, workspace: string]: nothing -> nothing {
    if ($workspace | is-empty) {
        print "❌ --workspace required for status"
        return
    }
    let ws_root = (_ws-root $workspace)
    if ($ws_root | is-empty) {
        print $"❌ Workspace '($workspace)' not found"
        return
    }
    let dim = (_read-fsm-state $name $ws_root)
    if ($dim | is-empty) {
        print $"No FSM dimension found for '($name)-status' in ($workspace)"
        return
    }
    let cur = ($dim | get -o current_state | default "—")
    let des = ($dim | get -o desired_state | default "—")
    let blk = ($dim | get -o transitions | default [] | get 0? | default {} | get -o blocker | default "")
    let cat = ($dim | get -o transitions | default [] | get 0? | default {} | get -o catalyst | default "")

    print $"($name) — FSM state [($workspace)]"
    print $"  current:  ($cur)"
    print $"  desired:  ($des)"
    if ($blk | is-not-empty) { print $"  blocker:  ($blk)" }
    if ($cat | is-not-empty) { print $"  catalyst: ($cat)" }
}
