#!/usr/bin/env nu
# domains/personal/commands.nu — PersonalOntology domain CLI for ontoref.
# Dispatched by the ontoref bash wrapper when repo_kind = PersonalOntology:
#   ore personal <command> [args]
#
# Reads data from ONTOREF_PROJECT_ROOT (set by the bash wrapper).
# Code lives in ONTOREF_ROOT/domains/personal/. Data lives in the project.

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.
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-core     [] { ^nickel export (resolve-onto-path "ontology/core.ncl")          | from json }
def load-state    [] { ^nickel export (resolve-onto-path "ontology/state.ncl")         | from json }
def load-career   [] { ^nickel export (resolve-onto-path "ontology/career.ncl")        | from json }
def load-personal [] { ^nickel export (resolve-onto-path "ontology/personal.ncl")      | from json }
def load-backlog  [] { ^nickel export (resolve-onto-path "reflection/backlog.ncl")      | from json }

# ─── Display helpers ─────────────────────────────────────────────────────────

def render-links [links: list] {
    if ($links | is-empty) { return }
    print ""
    print "links:"
    $links | each { |lk|
        let label = if ($lk.label? | default "" | is-not-empty) { $" — ($lk.label)" } else { "" }
        print $"  [($lk.kind)] ($lk.url)($label)"
    }
}

# ─── NCL patch helpers ────────────────────────────────────────────────────────

def ncl-validate [path: string] {
    ^nickel export $path | from json | ignore
}

def ncl-block-field-idx [lines: list<string>, target_id: string, field: string] {
    let id_idx = (
        $lines | enumerate
        | where { |l| $l.item | str contains $"\"($target_id)\"" }
        | first | get index
    )
    let next_id_idx = (
        $lines | enumerate | skip ($id_idx + 1)
        | where { |l| ($l.item | str trim | str starts-with "id ") and ($l.item | str contains "=") and ($l.item | str contains "\"") }
        | if ($in | is-empty) { [{ index: ($lines | length) }] } else { $in }
        | first | get index
    )
    $lines | enumerate
    | where { |l| $l.index > $id_idx and $l.index < $next_id_idx }
    | where { |l| $l.item | str trim | str starts-with $"($field) " }
    | first | get index
}

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

export def "state" [] {
    load-state | get dimensions | each { |dim|
        let cur = $dim.states | where id == $dim.current_state | first
        { dimension: $dim.id, current: $dim.current_state, desired: $dim.desired_state, tension: $cur.tension, 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: "–", horizon: "–" }
        } else {
            let tr = $active | first
            { dimension: $dim.id, from: $tr.from, to: $tr.to, condition: $tr.condition, blocker: $tr.blocker, catalyst: $tr.catalyst, horizon: $tr.horizon }
        }
    }
}

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,
        core_axioms:        ($core.nodes | where { |n| $n.id in $core.core } | select id level name),
        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",
    }
}

export def "audit" [] {
    let core = load-core
    let nodes = $core.nodes
    let edges = $core.edges
    let practice_ids = ($nodes | where level == "Practice" | get id)
    let tensions = ($nodes | where level == "Tension" | each { |t|
        let linked = ($edges | where kind == "ManifestsIn" | where { |e| $e.from == $t.id or $e.to == $t.id }
            | each { |e| let peer = if $e.from == $t.id { $e.to } else { $e.from }; if $peer in $practice_ids { $peer } else { null } } | compact)
        { tension: $t.id, name: $t.name, practice_count: ($linked | length), practices: ($linked | str join ", "), status: (if ($linked | is-empty) { "no practice" } else { "ok" }) }
    })
    let axioms = ($nodes | where level == "Axiom" | each { |a|
        let manifests = ($edges | where from == $a.id and kind == "ManifestsIn" | each { |e| $nodes | where id == $e.to | first })
        { axiom: $a.id, name: $a.name, total_links: ($manifests | length), project_links: ($manifests | where level == "Project" | length), status: (if ($manifests | where level == "Project" | is-empty) { "no project link" } else { "ok" }) }
    })
    { tensions: $tensions, axioms: $axioms, active_gap: $core.tension_without_practice }
}

# ─── projects ────────────────────────────────────────────────────────────────

export def "projects" [] {
    load-core | get nodes | where level == "Project" | select id name description
}

export def "projects show" [id: string] {
    let nodes = load-core | get nodes
    let r = if ($id =~ '^[0-9]+$') {
        let projects = ($nodes | where level == "Project")
        let idx = ($id | into int)
        if $idx >= ($projects | length) { print --stderr $"projects show: index out of range ($idx) — total ($projects | length)"; exit 1 }
        $projects | skip $idx | first
    } else {
        let found = ($nodes | where id == $id and level == "Project")
        if ($found | is-empty) { print --stderr $"projects show: not found — ($id)"; exit 1 }
        $found | first
    }
    print $"id:          ($r.id)"
    print $"name:        ($r.name)"
    print $"description: ($r.description)"
    if ($r.artifact_paths? | default [] | is-not-empty) {
        print ""
        print "artifacts:"
        $r.artifact_paths | each { |p| print $"  ($p)" }
    }
    render-links ($r.links? | default [])
}

# ─── career ───────────────────────────────────────────────────────────────────

export def "career skills" [--tier: string = ""] {
    let items = load-career | get skills
    let filtered = if ($tier | is-empty) { $items } else { $items | where tier == $tier }
    $filtered | sort-by --reverse proficiency | select name tier proficiency years
}

export def "career talks" [--status: string = ""] {
    let items = load-career | get talks
    let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
    $filtered | select id title event date status
}

export def "career publications" [--featured] {
    let items = load-career | get publications | sort-by sort_order
    let filtered = if $featured { $items | where featured == true } else { $items }
    $filtered | select project_node tagline status version featured
}

export def "career positioning" [] {
    load-career | get positioning | select name core_message target
}

# ─── content ──────────────────────────────────────────────────────────────────

export def "content" [--status: string = ""] {
    let items = load-personal | get contents
    let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
    $filtered | select id title kind status
}

# ─── opportunities ────────────────────────────────────────────────────────────

export def "opportunities" [--status: string = ""] {
    let items = load-personal | get opportunities
    let filtered = if ($status | is-empty) { $items } else { $items | where status == $status }
    $filtered | select id name kind status deadline
}

export def "opportunities show" [id: string] {
    let items = load-personal | get opportunities
    let r = if ($id =~ '^[0-9]+$') {
        let idx = ($id | into int)
        if $idx >= ($items | length) { print --stderr $"opportunities show: index out of range ($idx) — total ($items | length)"; exit 1 }
        $items | skip $idx | first
    } else {
        let found = ($items | where id == $id)
        if ($found | is-empty) { print --stderr $"opportunities show: not found — ($id)"; exit 1 }
        $found | first
    }
    print $"id:          ($r.id)"
    print $"name:        ($r.name)"
    print $"kind:        ($r.kind)"
    print $"status:      ($r.status)"
    print $"deadline:    ($r.deadline)"
    render-links ($r.links? | default [])
    if ($r.fit_signals | is-not-empty)  { print ""; print $"fit_signals: ($r.fit_signals | str join ', ')" }
    if ($r.linked_nodes | is-not-empty) { print ""; print $"nodes:       ($r.linked_nodes | str join ', ')" }
    print ""; print "note:"; print $"  ($r.note)"
}

export def "opportunities update" [id: string, --status: string] {
    let valid = ["Watching" "Evaluating" "Active" "Submitted" "Closed"]
    if ($status | is-empty) { print --stderr "opportunities update: --status required"; exit 1 }
    if not ($status in $valid) { print --stderr $"opportunities update: invalid status '($status)' — valid: ($valid | str join ', ')"; exit 1 }
    let items = load-personal | get opportunities
    if ($items | where id == $id | is-empty) { print --stderr $"opportunities update: not found — ($id)"; exit 1 }
    let path  = (resolve-onto-path "ontology/personal.ncl")
    let lines = open --raw $path | lines
    let status_idx = (ncl-block-field-idx $lines $id "status")
    let new_status = ($lines | get $status_idx | str replace --regex `'\w+` $"'($status)")
    let patched = ($lines | enumerate | each { |l| if $l.index == $status_idx { $new_status } else { $l.item } } | str join "\n")
    $patched | save --force $path
    ncl-validate $path
    print $"($id): status → '($status)"
}

# ─── cfp ──────────────────────────────────────────────────────────────────────

export def "cfp" [--stage: string = ""] {
    let items = load-backlog | get cfp
    let filtered = if ($stage | is-empty) { $items } else { $items | where stage == $stage }
    $filtered | select id name stage deadline sessionize_session_id next_action
}

export def "cfp show" [id: string] {
    let items = load-backlog | get cfp
    let r = if ($id =~ '^[0-9]+$') {
        let idx = ($id | into int)
        if $idx >= ($items | length) { print --stderr $"cfp show: index out of range ($idx) — total ($items | length)"; exit 1 }
        $items | skip $idx | first
    } else {
        let found = ($items | where id == $id)
        if ($found | is-empty) { print --stderr $"cfp show: not found — ($id)"; exit 1 }
        $found | first
    }
    print $"id:                    ($r.id)"
    print $"name:                  ($r.name)"
    print $"stage:                 ($r.stage)"
    print $"deadline:              ($r.deadline)"
    print $"opportunity_id:        ($r.opportunity_id)"
    print $"sessionize_session_id: ($r.sessionize_session_id)"
    print $"related_mode:          ($r.related_mode)"
    print $"created:               ($r.created)"
    print $"updated:               ($r.updated)"
    render-links ($r.links? | default [])
    print ""; print "next_action:"; print $"  ($r.next_action)"
    print ""; print "note:";        print $"  ($r.note)"
}

export def "cfp update" [id: string, --stage: string] {
    let valid = ["Watching" "Evaluating" "Drafting" "Submitted" "Accepted" "Declined" "Delivered"]
    if ($stage | is-empty) { print --stderr "cfp update: --stage required"; exit 1 }
    if not ($stage in $valid) { print --stderr $"cfp update: invalid stage '($stage)' — valid: ($valid | str join ', ')"; exit 1 }
    let items = load-backlog | get cfp
    if ($items | where id == $id | is-empty) { print --stderr $"cfp update: not found — ($id)"; exit 1 }
    let today = (date now | format date "%Y-%m-%d")
    let path  = (resolve-onto-path "reflection/backlog.ncl")
    let lines = open --raw $path | lines
    let stage_idx   = (ncl-block-field-idx $lines $id "stage")
    let updated_idx = (ncl-block-field-idx $lines $id "updated")
    let new_stage   = ($lines | get $stage_idx   | str replace --regex `'\w+`      $"'($stage)")
    let new_updated = ($lines | get $updated_idx  | str replace --regex `"[0-9-]+"` $"\"($today)\"")
    let patched = ($lines | enumerate | each { |l|
        if $l.index == $stage_idx { $new_stage } else if $l.index == $updated_idx { $new_updated } else { $l.item }
    } | str join "\n")
    $patched | save --force $path
    ncl-validate $path
    print $"($id): stage → '($stage)  |  updated → ($today)"
}

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

def show-help [] {
    print "Personal Ontology — ontoref domain extension"
    print ""
    print "USAGE"
    print "  ore personal <command> [args]"
    print ""
    print "COMMANDS"
    print "  projects                           Project portfolio (Project-level nodes)"
  print "  projects show <id|idx>             Full detail of a project node"
  print "  state                              Current FSM position across all dimensions"
    print "  next                               Next valid transition per dimension"
    print "  validate <decision>                Check against ontological invariants"
    print "  audit                              Coherence audit: gaps in coverage"
    print "  career skills [--tier]             Skills by proficiency"
    print "  career talks [--status]            Talks filtered by status"
    print "  career publications [--featured]   Publication cards"
    print "  career positioning                 Positioning strategies"
    print "  content [--status]                 Content pipeline"
    print "  opportunities [--status]           Opportunities"
    print "  opportunities show <id>            Full opportunity detail"
    print "  opportunities update <id>          Update opportunity status"
    print "  cfp [--stage]                      CFP pipeline"
    print "  cfp show <id>                      Full CFP detail"
    print "  cfp update <id> --stage <s>        Update CFP stage"
}

def main [
    ...args: string
    --tier:     string = ""
    --status:   string = ""
    --stage:    string = ""
    --featured
] {
    if ($args | is-empty) or ($args | first) == "help" { show-help; return }
    let sub = ($args | str join " ")
    match $sub {
        "state"                => { state }
        "next"                 => { next }
        "audit"                => { audit }
        "projects"             => { projects }
        "projects show"        => { print --stderr "usage: personal projects show <id|idx>"; exit 1 }
        _ if ($sub | str starts-with "projects show ") => { projects show ($args | get 2) }
        _ if ($sub | str starts-with "projects ")      => { projects show ($args | get 1) }
        _ if ($sub | str starts-with "validate ") => { validate ($args | skip 1 | str join " ") }
        "career skills"        => { career skills --tier $tier }
        "career talks"         => { career talks --status $status }
        "career publications"  => { if $featured { career publications --featured } else { career publications } }
        "career positioning"   => { career positioning }
        "content"              => { content --status $status }
        "opportunities"        => { opportunities --status $status }
        "opportunities show"   => { print --stderr "usage: personal opportunities show <id|idx>"; exit 1 }
        _ if ($sub | str starts-with "opportunities show ")   => { opportunities show ($args | get 2) }
        _ if ($sub | str starts-with "opportunities update ") => { opportunities update ($args | get 2) --status $status }
        _ if ($sub | str starts-with "opportunities ")        => { opportunities show ($args | get 1) }
        "cfp"                  => { cfp --stage $stage }
        "cfp show"             => { print --stderr "usage: personal cfp show <id|idx>"; exit 1 }
        _ if ($sub | str starts-with "cfp show ")   => { cfp show ($args | get 2) }
        _ if ($sub | str starts-with "cfp update ") => { cfp update ($args | get 2) --stage $stage }
        _ if ($sub | str starts-with "cfp ")        => { cfp show ($args | get 1) }
        _                      => { print $"Unknown command: ($sub)"; show-help }
    }
}
