#!/usr/bin/env nu
# reflection/modules/adr.nu — ADR management commands.
#
# Error handling policy:
#   adr list / constraints  — graceful skip via do { ^nickel export } | complete
#                             (draft ADRs may be incomplete; list must always work)
#   adr validate            — ^nickel export | complete for fresh data (no plugin cache)
#   adr show                — fail loud (caller passes a known valid id)

use env.nu *
use store.nu [daemon-export, daemon-export-safe]

# Formats an ADR record as readable markdown text.
def adr-to-md []: record -> string {
  let a = $in
  mut lines = []

  $lines = ($lines | append $"# ($a.id): ($a.title)")
  $lines = ($lines | append $"")
  $lines = ($lines | append $"**Status**: ($a.status)  **Date**: ($a.date)")
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Context")
  $lines = ($lines | append $a.context)
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Decision")
  $lines = ($lines | append $a.decision)
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Rationale")
  for r in $a.rationale {
    $lines = ($lines | append $"- **($r.claim)**")
    $lines = ($lines | append $"  ($r.detail)")
  }
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Consequences")
  $lines = ($lines | append "**Positive**")
  for p in $a.consequences.positive {
    $lines = ($lines | append $"- ($p)")
  }
  $lines = ($lines | append "**Negative**")
  for n in $a.consequences.negative {
    $lines = ($lines | append $"- ($n)")
  }
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Alternatives Considered")
  for alt in $a.alternatives_considered {
    $lines = ($lines | append $"- **($alt.option)**: ($alt.why_rejected)")
  }
  $lines = ($lines | append $"")

  $lines = ($lines | append "## Constraints")
  for c in $a.constraints {
    $lines = ($lines | append $"### ($c.id) [($c.severity)]")
    $lines = ($lines | append $c.claim)
    $lines = ($lines | append $"- Scope: ($c.scope)")
    $lines = ($lines | append $"- Check: `($c.check_hint)`")
    $lines = ($lines | append $"- Rationale: ($c.rationale)")
    $lines = ($lines | append $"")
  }

  let oc = $a.ontology_check
  $lines = ($lines | append "## Ontology Check")
  $lines = ($lines | append $"- Verdict: **($oc.verdict)**")
  if ($oc.invariants_at_risk | is-not-empty) {
    $lines = ($lines | append $"- Invariants at risk: ($oc.invariants_at_risk | str join ', ')")
  }
  if ($a.related_adrs | is-not-empty) {
    $lines = ($lines | append $"")
    $lines = ($lines | append $"## Related ADRs")
    $lines = ($lines | append ($a.related_adrs | str join ", "))
  }

  $lines | str join "\n"
}

# Resolve effective format: explicit flag wins; otherwise md for humans, json for agents.
def resolve-fmt [fmt: string, human_default: string]: nothing -> string {
  if ($fmt | is-not-empty) { return $fmt }
  let actor = ($env.ONTOREF_ACTOR? | default "developer")
  if $actor == "agent" { "json" } else { $human_default }
}

# Serialize or render a value in the requested format.
# fmt: "md" (default readable markdown) | "table" (Nu expand) | "json" | "yaml" | "toml"
def fmt-output [fmt: string]: any -> any {
  let data = $in
  match $fmt {
    "json"  => { $data | to json },
    "yaml"  => { $data | to yaml },
    "toml"  => {
      # to toml requires a record at the top level — wrap lists
      if (($data | describe) =~ "^(list|table)") {
        { items: $data } | to toml
      } else {
        $data | to toml
      }
    },
    "table" => { $data | table --expand },
    _       => {
      if (($data | describe) =~ "^record") {
        $data | adr-to-md
      } else {
        # list/table — render as markdown table
        $data | to md
      }
    },
  }
}

# Print a value — needed inside while loops where return values are discarded.
def print-output [fmt: string]: any -> nothing {
  let data = $in
  match $fmt {
    "json" | "yaml" | "toml" => { print $data },
    "table" => { print ($data | table --expand) },
    _ => {
      if (($data | describe) =~ "^record") {
        print ($data | adr-to-md)
      } else {
        print ($data | to md)
      }
    },
  }
}

export def "adr list" [
  --fmt: string = "",  # Output format: table (human) | json (agent) | yaml | toml
] {
  let f = (resolve-fmt $fmt "table")
  adr-files | each { |ncl|
    let data = (daemon-export-safe $ncl)
    if $data != null { $data | select id title status date } else { null }
  } | compact | sort-by date | fmt-output $f
}

export def "adr validate" [] {
  let hard = (
    adr-files | each { |ncl|
      daemon-export-safe $ncl
    }
    | compact
    | where status == "Accepted"
    | get constraints
    | flatten
    | where severity == "Hard"
  )

  let count = ($hard | length)
  print $"Running ($count) Hard constraints..."
  print ""

  let results = $hard | each { |c|
    let result = do { nu -c $c.check_hint } | complete
    {
      id:     $c.id,
      claim:  $c.claim,
      passed: ($result.exit_code == 0 and ($result.stdout | str trim | is-empty)),
      output: ($result.stdout | str trim),
    }
  }

  for r in $results {
    let icon = if $r.passed { "✓" } else { "✗" }
    print $"($icon) ($r.id): ($r.claim)"
    if not $r.passed and ($r.output | is-not-empty) {
      print $"  Violation: ($r.output)"
    }
  }

  let failures = $results | where passed == false
  if ($failures | is-not-empty) {
    let n = ($failures | length)
    error make { msg: $"($n) Hard constraints violated" }
  }
}

export def "adr show" [
  id?: string,            # ADR id: "001", "adr-001", or "adr-001-slug"
  --interactive (-i),     # Select from list interactively via typedialog
  --fmt: string = "",     # Output format: md (human) | json (agent) | table | yaml | toml
] {
  let f = (resolve-fmt $fmt "md")
  if $interactive or ($id | is-empty) {
    adr-show-interactive $f
  } else {
    adr-show-by-id $id $f
  }
}

export def "constraints" [
  --fmt: string = "",  # Output format: table (human) | json (agent) | yaml | toml
] {
  let f = (resolve-fmt $fmt "table")
  adr-files | each { |ncl|
    daemon-export-safe $ncl
  }
  | compact
  | where status == "Accepted"
  | each { |a| $a.constraints | each { |c| $c | merge { adr: $a.id } } }
  | flatten
  | where severity == "Hard"
  | select adr id claim check_hint
  | fmt-output $f
}

export def "adr help" [] {
  let actor = ($env.ONTOREF_ACTOR? | default "developer")
  let cmd   = ($env.ONTOREF_CALLER? | default "./onref")
  print ""
  print "ADR commands:"
  print $"  ($cmd) adr list             list all ADRs with status"
  print $"  ($cmd) adr list --fmt json  output as json/yaml/toml"
  print $"  ($cmd) adr validate         run Hard constraint checks"
  print $"  ($cmd) adr show             interactive ADR browser (no args)"
  print $"  ($cmd) adr show <id>        show a specific ADR"
  print $"  ($cmd) constraints          show active Hard constraints"
  if $actor == "agent" {
    print ""
    print "Agent query pattern:"
    print "  nickel-export adrs/adr-NNN-name.ncl"
    print "  nickel-export adrs/adr-NNN-name.ncl | get constraints | where severity == 'Hard'"
  }
  print ""
}

# ── Internal ───────────────────────────────────────────────────────────────────

export def "adr accept" [
  id: string,  # adr-NNN or NNN
] {
  let root      = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)
  let canonical = if ($id | str starts-with "adr-") { $id } else { $"adr-($id)" }
  let files     = glob $"($root)/adrs/($canonical)-*.ncl"
  if ($files | is-empty) {
    error make { msg: $"ADR '($id)' not found in adrs/" }
  }
  let path = ($files | first)
  let content = open $path
  if not ($content | str contains "'Proposed") {
    error make { msg: $"ADR '($id)' is not in Proposed status — current file does not contain \"'Proposed\"" }
  }
  $content | str replace --all "'Proposed" "'Accepted" | save --force $path
  print $"  accepted: ($path | path basename)"
  do { ^nickel typecheck $path } | complete
  | if $in.exit_code != 0 {
      error make { msg: $"typecheck failed after accept:\n($in.stderr)" }
    } else {
      print "  typecheck: ok"
    }
}

def adr-files [] {
  let root = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)
  glob $"($root)/adrs/adr-*.ncl"
}

def adr-show-by-id [id: string, fmt: string] {
  let root      = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)
  let canonical = if ($id | str starts-with "adr-") { $id } else { $"adr-($id)" }
  let files     = glob $"($root)/adrs/($canonical)-*.ncl"
  if ($files | is-empty) {
    error make { msg: $"ADR '($id)' not found in adrs/" }
  }
  let data = (daemon-export ($files | first))
  $data | fmt-output $fmt
}

def adr-show-interactive [fmt: string] {
  let entries = (
    adr-files | each { |ncl|
      let data = (daemon-export-safe $ncl)
      if $data != null { $data | select id title status } else { null }
    } | compact | sort-by id
  )

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

  let options = ($entries | each { |a| $"($a.id)  ($a.title)  [($a.status)]" })
  let menu    = ($options | append "— Exit —")

  mut browsing = true
  while $browsing {
    let choice = (typedialog select "Select ADR:" $menu)
    if $choice == "— Exit —" {
      $browsing = false
    } else {
      let idx        = ($options | enumerate | where { |r| $r.item == $choice } | first).index
      let selected   = ($entries | get $idx)
      print ""
      adr-show-by-id $selected.id $fmt | print-output $fmt
      print ""
      let next = (typedialog select "Continue?" ["Browse" "Exit"])
      if $next == "Exit" { $browsing = false }
    }
  }
}
