#!/usr/bin/env nu
# domains/framework/commands.nu — Framework protocol CLI for ontoref.
# Dispatched by the ontoref bash wrapper when repo_kind in {Library, Service, Tool}:
#   ore framework <command> [args]   or   ore fw <command> [args]
#
# Provides protocol connectivity commands (state, connections, gates, capabilities)
# for projects that are frameworks, libraries, or services — no workspace-specific ops.

def project-root [] {
    $env.ONTOREF_PROJECT_ROOT? | default (pwd | path expand)
}

# Resolves an ontoref section path against ADR-032 layout (canonical
# `.ontoref/<rel>`) falling back to legacy root-level layout for projects that
# have not yet applied migration 0023. Pass logical paths like
# `ontology/state.ncl`, `card.ncl`, etc.
def resolve-onto-path [logical: string] {
    let root = (project-root)
    let canonical = $"($root)/.ontoref/($logical)"
    if ($canonical | path exists) { return $canonical }
    let legacy_rel = if ($logical | str starts-with "ontology/") {
        $logical | str replace "ontology/" ".ontology/"
    } else { $logical }
    let legacy = $"($root)/($legacy_rel)"
    if ($legacy | path exists) { return $legacy }
    $canonical
}

# ─── Loaders ──────────────────────────────────────────────────────────────────

def load-state       [] { ^nickel export (resolve-onto-path "ontology/state.ncl")       | from json }
def load-core        [] { ^nickel export (resolve-onto-path "ontology/core.ncl")         | from json }
def load-manifest    [] { ^nickel export (resolve-onto-path "ontology/manifest.ncl")     | from json }

def load-optional [path: string] {
    if ($path | path exists) { ^nickel export $path | from json } else { null }
}

# ─── state / next / validate ──────────────────────────────────────────────────

export def "state" [] {
    load-state | get dimensions | each { |dim|
        { dimension: $dim.id, current: $dim.current_state, desired: $dim.desired_state, horizon: $dim.horizon }
    }
}

export def "next" [] {
    load-state | get dimensions | each { |dim|
        let active = $dim.transitions | where from == $dim.current_state
        if ($active | is-empty) {
            { dimension: $dim.id, from: $dim.current_state, to: "–", condition: "no active transition", blocker: "–", catalyst: "–" }
        } else {
            let tr = $active | first
            { dimension: $dim.id, from: $tr.from, to: $tr.to, condition: $tr.condition, blocker: $tr.blocker, catalyst: $tr.catalyst }
        }
    }
}

export def "validate" [decision: string] {
    let core  = load-core
    let words = ($decision | str downcase | split row " " | where { |w| ($w | str length) > 3 })
    let affected = ($core.nodes | each { |n|
        let text = $"($n.id) ($n.name) ($n.description)" | str downcase
        if ($words | any { |w| $text =~ $w }) { $n } else { null }
    } | compact)
    {
        decision:           $decision,
        invariants_at_risk: ($affected | where invariant == true | select id level name),
        tensions_touched:   ($affected | where level == "Tension" | select id name),
        practices_touched:  ($affected | where level == "Practice" | select id name),
        verdict:            "manual review required — if any invariant is contradicted, justification is required",
    }
}

# ─── connections / gates ──────────────────────────────────────────────────────

export def "connections" [] {
    let path = (resolve-onto-path "ontology/connections.ncl")
    let c = load-optional $path
    if ($c | is-empty) {
        print --stderr "No connections.ncl found for this project."
        exit 1
    }
    if ($c.upstream? | default [] | is-not-empty) {
        print ""
        print "UPSTREAM"
        $c.upstream | each { |u|
            print $"  ($u.project)  kind=($u.kind)  via=($u.via)"
            if ($u.node? | default "" | is-not-empty) { print $"    node: ($u.node)" }
            print $"    ($u.note)"
        }
    }
    if ($c.downstream? | default [] | is-not-empty) {
        print ""
        print "DOWNSTREAM"
        $c.downstream | each { |d|
            print $"  ($d.project)  kind=($d.kind)  via=($d.via)"
            if ($d.node? | default "" | is-not-empty) { print $"    node: ($d.node)" }
            print $"    ($d.note)"
        }
    }
    if ($c.peers? | default [] | is-not-empty) {
        print ""
        print "PEERS"
        $c.peers | each { |p| print $"  ($p.project)  kind=($p.kind)  via=($p.via)" }
    }
    if ($c.upstream? | default [] | is-empty) and ($c.downstream? | default [] | is-empty) and ($c.peers? | default [] | is-empty) {
        print "No connections declared."
    }
}

export def "gates" [] {
    let path = (resolve-onto-path "ontology/gate.ncl")
    let g = load-optional $path
    if ($g | is-empty) {
        print "No gate.ncl found for this project."
        return
    }
    $g | get membranes | each { |m|
        {
            membrane:     $m.id,
            name:         $m.name,
            active:       $m.active,
            permeability: $m.permeability,
            condition:    ($m.opening_condition.description? | default ""),
        }
    }
}

# ─── capabilities ─────────────────────────────────────────────────────────────

export def "capabilities" [] {
    let caps = load-manifest | get capabilities? | default []
    if ($caps | is-empty) { print "No capabilities declared in manifest."; return }
    $caps | select id name summary
}

# ─── help + main ──────────────────────────────────────────────────────────────

def show-help [] {
    print "Framework — ontoref domain extension"
    print ""
    print "USAGE"
    print "  ore framework <command> [args]   (alias: ore fw)"
    print ""
    print "COMMANDS"
    print "  state                     Current FSM position across all dimensions"
    print "  next                      Next valid transitions with blockers/catalysts"
    print "  connections               Upstream/downstream project dependency graph"
    print "  gates                     Gate membrane status and opening conditions"
    print "  capabilities              Project capabilities from manifest"
    print "  validate <decision>       Check decision against ontological invariants"
}

def main [...args: string] {
    if ($args | is-empty) or ($args | first) == "help" { show-help; return }
    let sub = ($args | str join " ")
    match $sub {
        "state"        => { state }
        "next"         => { next }
        "connections"  => { connections }
        "gates"        => { gates }
        "capabilities" => { capabilities }
        _ if ($sub | str starts-with "validate ") => { validate ($args | skip 1 | str join " ") }
        _ => { print --stderr $"Unknown command: ($sub)"; show-help; exit 1 }
    }
}
