use ../lib_provisioning/user/config.nu [get-workspace-path, get-active-workspace-details]
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval, ncl-eval-soft, default-ncl-paths]

# Resolve provisioning root from env with default fallback.
def oq-prov-root []: nothing -> string {
    $env.PROVISIONING? | default "/usr/local/provisioning"
}

# Export a Nickel file as parsed JSON using workspace + provisioning import paths.
def oq-ncl-export [ws_root: string, full_path: string]: nothing -> record {
    ncl-eval $full_path (default-ncl-paths $ws_root)
}

# Resolve workspace name from optional arg or active workspace.
def oq-resolve-ws [workspace: string]: nothing -> record {
    let ws_name = if ($workspace | is-not-empty) {
        $workspace
    } else {
        let details = (get-active-workspace-details)
        if ($details == null) {
            error make { msg: "No active workspace. Pass --workspace or activate one first." }
        }
        $details.name
    }
    let ws_root = (get-workspace-path $ws_name)
    if ($ws_root | is-empty) or ($ws_root == null) {
        error make { msg: $"Workspace '($ws_name)' not found in registry." }
    }
    { name: $ws_name, root: $ws_root }
}

# Detect infra subdirectory: first dir under infra/ that contains settings.ncl.
def oq-detect-infra [ws_root: string]: nothing -> string {
    let result = (do { ^bash -c $"ls -1d ($ws_root)/infra/*/settings.ncl 2>/dev/null | head -1" } | complete)
    if $result.exit_code != 0 or ($result.stdout | str trim | is-empty) {
        error make { msg: $"No infra/*/settings.ncl found under ($ws_root)" }
    }
    let parts = ($result.stdout | str trim | path split)
    # path: ws_root/infra/<name>/settings.ncl — index -2 is infra name.
    $parts | get ($parts | length | $in - 2)
}

# Collect all workflow *.ncl files under infra/{infra}/workflows/.
def oq-collect-workflows [ws_root: string]: nothing -> list {
    let result = (do { ^bash -c $"ls ($ws_root)/infra/*/workflows/*.ncl 2>/dev/null" } | complete)
    if $result.exit_code != 0 or ($result.stdout | str trim | is-empty) {
        return []
    }
    $result.stdout | lines | where { $in | str trim | is-not-empty }
}

# Load settings.ncl components for the auto-detected infra.
def oq-load-components [ws_root: string]: nothing -> record {
    let infra  = (oq-detect-infra $ws_root)
    let path   = ($ws_root | path join "infra" $infra "settings.ncl")
    if not ($path | path exists) {
        return {}
    }
    let exported = (oq-ncl-export $ws_root $path)
    $exported | get -o components | default {}
}

# Show the unified view of a component: config, FSM dimension state, and ontology consumers.
#
# Reads infra/{infra}/components/{name}.ncl for config, .ontology/state.ncl for dimension
# state, and .ontology/core.ncl for edges referencing this component.
export def "main describe component" [
    name: string                # Component name (e.g. postgresql, forgejo)
    --workspace (-w): string    # Workspace name (default: active)
] : nothing -> record {
    let ws = (oq-resolve-ws $workspace)
    let ws_root = $ws.root

    let infra       = (oq-detect-infra $ws_root)
    let comp_path   = ($ws_root | path join "infra" $infra "components" $"($name).ncl")
    let settings_path = ($ws_root | path join "infra" $infra "settings.ncl")

    # Component source config from its own NCL file.
    let source_cfg = if ($comp_path | path exists) {
        (oq-ncl-export $ws_root $comp_path) | get -o $name | default {}
    } else if ($settings_path | path exists) {
        let settings = (oq-ncl-export $ws_root $settings_path)
        $settings | get -o components | default {} | get -o $name | default {}
    } else {
        {}
    }

    let mode       = ($source_cfg | get -o mode | default "unknown" | into string | str replace "'" "")
    let requires   = ($source_cfg | get -o requires | default {})
    let provides   = ($source_cfg | get -o provides | default {})
    let operations = ($source_cfg | get -o operations | default {})

    # Extension path.
    let prov_root = (oq-prov-root)
    let ext_path  = ($prov_root | path join "extensions/components" $name)

    # FSM dimension: look for dimension id matching "{name}-status".
    let state_path = ($ws_root | path join ".ontology" "state.ncl")
    let fsm_state = if ($state_path | path exists) {
        let state_data = (ncl-eval-soft $state_path (default-ncl-paths $ws_root) null)
        if ($state_data | is-not-empty) {
            let dims = ($state_data | get -o dimensions | default [])
            let dim_id = $"($name)-status"
            let dim = ($dims | where {|d| $d.id == $dim_id})
            if ($dim | is-empty) {
                { dimension: null, current_state: "unknown", desired_state: "unknown" }
            } else {
                let d = ($dim | first)
                {
                    dimension:     $dim_id,
                    current_state: ($d | get -o current_state | default "unknown"),
                    desired_state: ($d | get -o desired_state | default "unknown"),
                }
            }
        } else {
            { dimension: null, current_state: "unknown", desired_state: "unknown" }
        }
    } else {
        { dimension: null, current_state: "unknown", desired_state: "unknown" }
    }

    # Ontology consumers: edges in core.ncl that reference this component name.
    let core_path = ($ws_root | path join ".ontology" "core.ncl")
    let consumers = if ($core_path | path exists) {
        let core_data = (ncl-eval-soft $core_path (default-ncl-paths $ws_root) null)
        if ($core_data | is-not-empty) {
            let edges = ($core_data | get -o edges | default [])
            $edges | where {|e|
                ($e | get -o from | default "") == $name or ($e | get -o to | default "") == $name
            } | each {|e| { from: ($e | get -o from | default ""), to: ($e | get -o to | default ""), kind: ($e | get -o kind | default "") }}
        } else { [] }
    } else { [] }

    {
        name:           $name,
        mode:           $mode,
        requires:       $requires,
        provides:       $provides,
        operations:     $operations,
        state:          $fsm_state,
        consumers:      $consumers,
        extension_path: $ext_path,
    }
}

# List all components that expose database services.
#
# Filters components where provides.databases is non-empty, returning a flat table
# with one row per component.
export def "main describe databases" [
    --workspace (-w): string    # Workspace name (default: active)
] : nothing -> table {
    let ws = (oq-resolve-ws $workspace)
    let ws_root = $ws.root
    let components = (oq-load-components $ws_root)

    $components | columns | each {|comp_name|
        let comp = ($components | get $comp_name)
        let provides  = ($comp | get -o provides | default {})
        let databases = ($provides | get -o databases | default [])
        if ($databases | is-not-empty) {
            let port = ($provides | get -o port | default ($comp | get -o port | default 0))
            let requires = ($comp | get -o requires | default {})
            let ns_raw = ($comp | get -o namespace | default "default")
            {
                component: $comp_name,
                databases: ($databases | str join ", "),
                port:      $port,
                namespace: $ns_raw,
            }
        } else {
            null
        }
    } | where { $in != null }
}

# List all components deployed to a specific Kubernetes namespace.
export def "main describe namespace" [
    namespace: string           # Kubernetes namespace to filter on
    --workspace (-w): string    # Workspace name (default: active)
] : nothing -> table {
    let ws = (oq-resolve-ws $workspace)
    let ws_root = $ws.root
    let components = (oq-load-components $ws_root)

    $components | columns | each {|comp_name|
        let comp = ($components | get $comp_name)
        let comp_ns = ($comp | get -o namespace | default "")
        if $comp_ns == $namespace {
            let mode_raw = ($comp | get -o mode | default "unknown" | into string | str replace "'" "")
            let port = ($comp | get -o port | default ($comp | get -o requires | default {} | get -o ports | default [] | first | default {} | get -o port | default 0))
            let image = ($comp | get -o image | default "")
            {
                component: $comp_name,
                mode:      $mode_raw,
                port:      $port,
                image:     $image,
            }
        } else {
            null
        }
    } | where { $in != null }
}

# Show storage topology: available classes from capabilities.ncl and per-component requirements.
export def "main describe storage" [
    --workspace (-w): string    # Workspace name (default: active)
] : nothing -> record {
    let ws = (oq-resolve-ws $workspace)
    let ws_root = $ws.root
    let infra    = (oq-detect-infra $ws_root)
    let prov_root = (oq-prov-root)

    # Available storage classes from capabilities.ncl.
    let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl")
    let available_classes = if ($caps_path | path exists) {
        ncl-eval-soft $caps_path [$ws_root $prov_root] {} | get -o provides | default {} | get -o storage_classes | default []
    } else { [] }

    # Per-component storage requirements.
    let components = (oq-load-components $ws_root)
    let component_requirements = ($components | columns | each {|comp_name|
        let comp    = ($components | get $comp_name)
        let requires = ($comp | get -o requires | default {})
        let storage  = ($requires | get -o storage | default null)
        if $storage != null {
            {
                component:     $comp_name,
                size:          ($storage | get -o size | default ""),
                storage_class: ($storage | get -o storage_class | default ($comp | get -o storage_class | default "")),
                persistent:    ($storage | get -o persistent | default false),
            }
        } else {
            null
        }
    } | where { $in != null })

    {
        available_classes:      $available_classes,
        component_requirements: $component_requirements,
    }
}

# Show a full workflow definition with FSM state and backlog references.
#
# Finds the workflow by id across all infra/*/workflows/*.ncl files and returns
# its steps, FSM dimension state, and any backlog_refs declared in metadata.
export def "main describe workflow" [
    workflow_id: string         # Workflow id to describe
    --workspace (-w): string    # Workspace name (default: active)
] : nothing -> record {
    let ws = (oq-resolve-ws $workspace)
    let ws_root  = $ws.root
    let prov_root = (oq-prov-root)

    let wf_files = (oq-collect-workflows $ws_root)
    if ($wf_files | is-empty) {
        error make { msg: $"No workflow files found under ($ws_root)/infra/*/workflows/" }
    }

    mut wf_def  = null
    mut wf_meta = null

    for wf_file in $wf_files {
        let exported = (oq-ncl-export $ws_root $wf_file)
        for key in ($exported | columns) {
            let entry = ($exported | get $key)
            if ($key | str ends-with "metadata") {
                if ($entry | get -o id | default "") == $workflow_id {
                    $wf_meta = $entry
                }
            } else {
                if ($entry | get -o id | default "") == $workflow_id {
                    $wf_def = $entry
                }
            }
        }
        if $wf_def != null { break }
    }

    if $wf_def == null {
        error make { msg: $"Workflow '($workflow_id)' not found in any infra/*/workflows/*.ncl under ($ws_root)" }
    }

    # FSM dimension state.
    let fsm_dim = if $wf_meta != null {
        $wf_meta | get -o fsm_dimension | default ""
    } else { "" }

    let fsm_state = if ($fsm_dim | is-not-empty) {
        let state_path = ($ws_root | path join ".ontology" "state.ncl")
        if ($state_path | path exists) {
            let state_data2 = (ncl-eval-soft $state_path (default-ncl-paths $ws_root) null)
            if ($state_data2 | is-not-empty) {
                let dims = ($state_data2 | get -o dimensions | default [])
                let dim = ($dims | where {|d| $d.id == $fsm_dim})
                if ($dim | is-empty) {
                    { dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
                } else {
                    let d = ($dim | first)
                    {
                        dimension:     $fsm_dim,
                        current_state: ($d | get -o current_state | default "unknown"),
                        desired_state: ($d | get -o desired_state | default "unknown"),
                    }
                }
            } else {
                { dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
            }
        } else {
            { dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
        }
    } else {
        { dimension: null, current_state: "unknown", desired_state: "unknown" }
    }

    {
        id:           $workflow_id,
        name:         (if $wf_meta != null { $wf_meta | get -o name | default $workflow_id } else { $workflow_id }),
        description:  (if $wf_meta != null { $wf_meta | get -o description | default "" } else { $wf_def | get -o description | default "" }),
        steps:        ($wf_def | get -o steps | default []),
        fsm_state:    $fsm_state,
        backlog_refs: (if $wf_meta != null { $wf_meta | get -o backlog_refs | default [] } else { [] }),
    }
}
