#!/usr/bin/env nu
# reflection/modules/graph.nu — actor-aware graph output.
#
# Agent  → Mermaid DSL (stdout, parseable, diffeable)
# Human  → URL to daemon UI (if running), else Mermaid
#
# Supported types:
#   ontology  — core.ncl nodes + edges as flowchart
#   flow      — last run steps as DAG
#   deps      — Rust crate dependency graph (if has_rust)
#   mode      — a reflection mode DAG

use ../modules/store.nu [daemon-export-safe]
use ../modules/describe.nu [nickel-import-path]

def project-root []: nothing -> string {
  let pr = ($env.ONTOREF_PROJECT_ROOT? | default "")
  if ($pr | is-not-empty) and ($pr != $env.ONTOREF_ROOT) { $pr } else { $env.ONTOREF_ROOT }
}

def actor-default []: nothing -> string {
  $env.ONTOREF_ACTOR? | default "developer"
}

def daemon-url []: nothing -> string {
  $env.ONTOREF_DAEMON_URL? | default "http://127.0.0.1:7891"
}

def daemon-running []: nothing -> bool {
  let r = do { ^curl -sf $"(daemon-url)/health" } | complete
  $r.exit_code == 0
}

# Sanitize a string for use as a Mermaid node ID (no hyphens, dots, spaces).
def mermaid-id [s: string]: nothing -> string {
  $s | str replace --all "-" "_" | str replace --all "." "_" | str replace --all " " "_"
}

# Emit Mermaid or UI URL based on actor and daemon availability.
def emit-graph [mermaid: string, ui_path: string, fmt: string, actor: string]: nothing -> nothing {
  if $fmt == "mermaid" or $actor == "agent" {
    print $mermaid
  } else if $fmt == "url" {
    print $"(daemon-url)($ui_path)"
  } else {
    # text/human: prefer URL if daemon running, else Mermaid
    if (daemon-running) {
      print $"Open in UI: (daemon-url)($ui_path)"
      print $"Or run: ONTOREF_ACTOR=agent ontoref graph (if ($ui_path | str contains "ontology") { "ontology" } else if ($ui_path | str contains "flow") { "flow" } else { "deps" }) --fmt mermaid"
    } else {
      print $mermaid
    }
  }
}

def ontology-to-mermaid [root: string]: nothing -> string {
  let ip = (nickel-import-path $root)
  let core_file = $"($root)/.ontology/core.ncl"
  if not ($core_file | path exists) { return "flowchart LR\n  note[\"No .ontology/core.ncl found\"]" }
  let core = (daemon-export-safe $core_file --import-path $ip)
  if $core == null { return "flowchart LR\n  note[\"Failed to export core.ncl\"]" }

  let nodes = ($core.nodes? | default [])
  let edges = ($core.edges? | default [])

  mut lines = ["flowchart LR"]
  $lines = ($lines | append "  classDef axiom    fill:#1a1a2e,stroke:#e94560,color:#fff,font-weight:bold")
  $lines = ($lines | append "  classDef tension  fill:#16213e,stroke:#f5a623,color:#fff")
  $lines = ($lines | append "  classDef practice fill:#0f3460,stroke:#53c28b,color:#fff")
  $lines = ($lines | append "  classDef project  fill:#1b262c,stroke:#4fc3f7,color:#fff")
  $lines = ($lines | append "  classDef moment   fill:#2d2d2d,stroke:#aaa,color:#fff")
  $lines = ($lines | append "")

  for n in $nodes {
    let nid = (mermaid-id $n.id)
    let label = ($n.name? | default $n.id)
    let level = ($n.level? | default "Project" | str downcase)
    $lines = ($lines | append $"  ($nid)[\"($label)\"]:::($level)")
  }

  if ($edges | is-not-empty) {
    $lines = ($lines | append "")
    for e in $edges {
      let fid = (mermaid-id $e.from)
      let tid = (mermaid-id $e.to)
      let kind = ($e.kind? | default "")
      let label = if ($kind | is-not-empty) { $" -->|\"($kind)\"| " } else { " --> " }
      $lines = ($lines | append $"  ($fid)($label)($tid)")
    }
  }

  $lines | str join "\n"
}

def run-flow-to-mermaid [root: string, actor: string]: nothing -> string {
  let runs_dir = $"($root)/.coder/($actor)/runs"
  let current_file = $"($runs_dir)/current.json"
  if not ($current_file | path exists) { return "flowchart TD\n  note[\"No active run found\"]" }

  let current = (open $current_file)
  let run_id = ($current.run_id? | default "")
  let mode_id = ($current.mode? | default "")
  if ($run_id | is-empty) { return "flowchart TD\n  note[\"No active run\"]" }

  let steps_file = $"($runs_dir)/($run_id)/steps.jsonl"
  let reported = if ($steps_file | path exists) {
    open $steps_file | lines | where { |l| $l | str trim | is-not-empty } | each { |l| $l | from json }
  } else { [] }

  # Load mode DAG for structure
  let ontoref_file = $"($env.ONTOREF_ROOT)/reflection/modes/($mode_id).ncl"
  let project_file = $"($root)/reflection/modes/($mode_id).ncl"
  let mode_file = if ($project_file | path exists) { $project_file } else if ($ontoref_file | path exists) { $ontoref_file } else { "" }

  let steps = if ($mode_file | is-not-empty) {
    let ip = (nickel-import-path (if ($project_file | path exists) { $root } else { $env.ONTOREF_ROOT }))
    let m = (daemon-export-safe $mode_file --import-path $ip)
    if $m != null { $m.steps? | default [] } else { [] }
  } else { [] }

  mut lines = [$"flowchart TD", $"  classDef pass    fill:#1a472a,stroke:#53c28b,color:#fff", $"  classDef fail    fill:#4a0e0e,stroke:#e94560,color:#fff", $"  classDef skip    fill:#2d2d2d,stroke:#888,color:#aaa", $"  classDef pending fill:#1a1a2e,stroke:#555,color:#666", ""]

  for s in $steps {
    let sid = (mermaid-id $s.id)
    let rep_list = ($reported | where { |r| $r.step == $s.id })
    let rep = if ($rep_list | is-not-empty) { $rep_list | first } else { null }
    let status = if ($rep | is-not-empty) { $rep.status } else { "pending" }
    let warn = if ($rep | is-not-empty) and ($rep.warnings? | default 0) > 0 { $" ⚠($rep.warnings)" } else { "" }
    $lines = ($lines | append $"  ($sid)[\"($s.id)\\n($status)($warn)\"]:::($status)")
  }

  $lines = ($lines | append "")
  for s in $steps {
    let sid = (mermaid-id $s.id)
    for dep in ($s.depends_on? | default []) {
      let did = (mermaid-id $dep.step)
      let kind = ($dep.kind? | default "Always")
      let arrow = if $kind == "OnFailure" { " -.->|\"OnFailure\"| " } else { " --> " }
      $lines = ($lines | append $"  ($did)($arrow)($sid)")
    }
  }

  $lines | str join "\n"
}

def deps-to-mermaid [root: string]: nothing -> string {
  let cargo_toml = $"($root)/Cargo.toml"
  if not ($cargo_toml | path exists) { return "flowchart LR\n  note[\"No Cargo.toml found\"]" }

  let cargo = (open $cargo_toml)
  let members = ($cargo | get -o workspace.members | default [])

  if ($members | is-empty) {
    return "flowchart LR\n  note[\"Single-crate project — no inter-crate deps to show\"]"
  }

  # Collect crate names
  let crate_names = ($members | each { |m|
    glob $"($root)/($m)/Cargo.toml"
  } | flatten | each { |f|
    let c = (open $f)
    $c | get -o package.name | default ($f | path dirname | path basename)
  })

  # Collect inter-workspace dependencies
  mut edges = []
  for m in $members {
    let expanded = (glob $"($root)/($m)/Cargo.toml")
    for ct in $expanded {
      let c = (open $ct)
      let cname = ($c | get -o package.name | default ($ct | path dirname | path basename))
      let all_deps = (
        ($c | get -o dependencies | default {} | columns) ++
        ($c | get -o "dev-dependencies" | default {} | columns) ++
        ($c | get -o "build-dependencies" | default {} | columns)
      )
      for dep in $all_deps {
        if $dep in $crate_names {
          $edges = ($edges | append { from: $cname, to: $dep })
        }
      }
    }
  }

  mut lines = ["flowchart LR"]
  for n in $crate_names {
    let nid = (mermaid-id $n)
    $lines = ($lines | append $"  ($nid)[\"($n)\"]")
  }
  $lines = ($lines | append "")
  for e in $edges {
    let fid = (mermaid-id $e.from)
    let tid = (mermaid-id $e.to)
    $lines = ($lines | append $"  ($fid) --> ($tid)")
  }

  $lines | str join "\n"
}

def mode-to-mermaid [mode_id: string]: nothing -> string {
  let root = (project-root)
  let project_file = $"($root)/reflection/modes/($mode_id).ncl"
  let ontoref_file = $"($env.ONTOREF_ROOT)/reflection/modes/($mode_id).ncl"
  let mode_file = if ($project_file | path exists) { $project_file } else if ($ontoref_file | path exists) { $ontoref_file } else { "" }

  if ($mode_file | is-empty) { return $"flowchart TD\n  note[\"Mode '($mode_id)' not found\"]" }

  let mode_root = if ($project_file | path exists) { $root } else { $env.ONTOREF_ROOT }
  let ip = (nickel-import-path $mode_root)
  let m = (daemon-export-safe $mode_file --import-path $ip)
  if $m == null { return $"flowchart TD\n  note[\"Failed to export mode '($mode_id)'\"]" }

  let steps = ($m.steps? | default [])
  mut lines = [$"flowchart TD", $"  classDef human  fill:#16213e,stroke:#f5a623,color:#fff", $"  classDef agent  fill:#0f3460,stroke:#53c28b,color:#fff", $"  classDef both   fill:#1a1a2e,stroke:#4fc3f7,color:#fff", ""]

  for s in $steps {
    let sid = (mermaid-id $s.id)
    let actor_class = match ($s.actor? | default "Both") {
      "Human" => "human",
      "Agent" => "agent",
      _       => "both",
    }
    let cmd_hint = if ($s.cmd? | default "" | is-not-empty) { "\\n[cmd]" } else { "" }
    $lines = ($lines | append $"  ($sid)[\"($s.id)($cmd_hint)\"]:::($actor_class)")
  }

  $lines = ($lines | append "")
  for s in $steps {
    let sid = (mermaid-id $s.id)
    for dep in ($s.depends_on? | default []) {
      let did = (mermaid-id $dep.step)
      $lines = ($lines | append $"  ($did) --> ($sid)")
    }
  }

  $lines | str join "\n"
}

# Emit a graph. Type: ontology | flow | deps | mode:<id>
# Agent → Mermaid DSL. Human → daemon UI URL (if running) or Mermaid.
export def "graph show" [
  type: string = "ontology",   # ontology | flow | deps | mode:<id>
  --fmt (-f): string = "",     # mermaid | url | text (default: actor-aware)
  --actor: string = "",
]: nothing -> nothing {
  let root = (project-root)
  let a = if ($actor | is-not-empty) { $actor } else { (actor-default) }
  let f = if ($fmt | is-not-empty) { $fmt } else if $a == "agent" { "mermaid" } else { "text" }

  match $type {
    "ontology" => {
      let mmd = (ontology-to-mermaid $root)
      emit-graph $mmd "/graph" $f $a
    }
    "flow" => {
      let mmd = (run-flow-to-mermaid $root $a)
      emit-graph $mmd "/graph" $f $a
    }
    "deps" => {
      let mmd = (deps-to-mermaid $root)
      emit-graph $mmd "/graph" $f $a
    }
    _ => {
      # mode:<id> or just the mode id
      let mode_id = if ($type | str starts-with "mode:") { $type | str replace "mode:" "" } else { $type }
      let mmd = (mode-to-mermaid $mode_id)
      emit-graph $mmd "/graph" $f $a
    }
  }
}
