#!/usr/bin/env nu
# reflection/modules/register.nu — ontoref register implementation.
#
# Reads a structured change record (from register_change.ncl form output or
# piped JSON) and writes the appropriate artifacts:
#   - CHANGELOG entry (always)
#   - ADR hint        (if needs_adr = true — tells user the /create-adr command)
#   - .ontology/state.ncl patch  (if changes_ontology_state = true)
#   - reflection/modes/<id>.ncl stub  (if affects_capability + add)
#
# Ontology patch uses sed range pattern — no mutable closures.
# All modified files are nickel-typechecked before write.

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

export def "register run" [
  --backend: string = "cli",
] {
  let actor     = ($env.ONTOREF_ACTOR? | default "developer")
  let form_file = $"($env.ONTOREF_ROOT)/reflection/forms/register_change.ncl"
  let root      = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)

  if $actor == "agent" {
    print "Agent: pipe a JSON record matching register_change.ncl fields to 'register apply'"
    print $"  nickel export ($form_file) | get elements"
    print "    | where type != \"section_header\" | select name prompt required nickel_path"
    return
  }

  if (which typedialog | is-empty) {
    print "typedialog required for interactive register. Run: cargo install typedialog"
    exit 1
  }

  let tmp_out = (mktemp --suffix ".json")

  match $backend {
    "tui" => { ^typedialog tui nickel-roundtrip $form_file $form_file --output $tmp_out }
    "web" => { ^typedialog web nickel-roundtrip $form_file $form_file --output $tmp_out --open }
    _     => { ^typedialog nickel-roundtrip $form_file $form_file --output $tmp_out }
  }

  if not ($tmp_out | path exists) {
    print "register cancelled — no output written"
    return
  }

  let data = open $tmp_out | from json
  rm $tmp_out
  $data | register apply
}

# Apply a structured change record. Pipe JSON or call directly.
export def "register apply" [] {
  let data = $in
  let root = ($env.ONTOREF_PROJECT_ROOT? | default $env.ONTOREF_ROOT)

  if ($data.ready? | default true) == false {
    print "register: ready = false — no artifacts written"
    return
  }

  print ""
  print $"ontoref register — ($data.summary)"
  print "────────────────────────────────────────────────"

  # ── CHANGELOG ───────────────────────────────────────────────────────────────
  let changelog = $"($root)/CHANGELOG.md"
  if ($changelog | path exists) {
    register-changelog $data $changelog
  } else {
    print $"  skip   : CHANGELOG.md not found at ($changelog)"
  }

  # ── ADR hint ─────────────────────────────────────────────────────────────────
  if ($data.needs_adr? | default false) {
    let title  = ($data.adr_title? | default "")
    if ($title | str length) > 0 {
      let flag = if ($data.adr_accept_immediately? | default false) { "-a " } else { "" }
      print $"  adr    : run in a Claude session:"
      print $"           /create-adr ($flag)\"($title)\""
    }
  }

  # ── ONTOLOGY STATE ──────────────────────────────────────────────────────────
  if ($data.changes_ontology_state? | default false) {
    let dim_id    = ($data.ontology_dimension_id? | default "")
    let new_state = ($data.ontology_new_state? | default "")
    if ($dim_id | str length) > 0 and ($new_state | str length) > 0 {
      register-ontology-state $root $dim_id $new_state
    } else {
      print "  skip   : ontology — dimension_id or new_state missing"
    }
  }

  # ── MODE STUB ───────────────────────────────────────────────────────────────
  if ($data.affects_capability? | default false) {
    let action  = ($data.capability_action? | default "add")
    let mode_id = ($data.capability_mode_id? | default "")
    if $action == "add" and ($mode_id | str length) > 0 {
      register-mode-stub $root $mode_id $data.summary
    } else if $action != "add" {
      print $"  mode   : ($action) '($mode_id)' — edit reflection/modes/($mode_id).ncl manually"
    } else {
      print "  skip   : mode — capability_mode_id missing"
    }
  }

  # ── CONFIG SEAL ─────────────────────────────────────────────────────────────
  if ($data.seals_config_profile? | default false) {
    let profile = ($data.config_profile? | default "")
    if ($profile | str length) > 0 {
      let adr_ref  = ($data.adr_title? | default "")
      let note_ref = ($data.summary? | default "")
      print $"  config : sealing profile '($profile)' via ontoref config apply"
      let entry_bin = $"($root)/onref"
      do { ^$entry_bin config apply $profile --note $note_ref } | complete | ignore
    } else {
      print "  skip   : config seal — config_profile missing"
    }
  }

  print ""
  print "Done. Review written artifacts before committing."
  print ""
}

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

def changelog-section [change_type: string]: nothing -> string {
  match $change_type {
    "feature"       => "Added",
    "fix"           => "Fixed",
    "architectural" => "Changed",
    "refactor"      => "Changed",
    "tooling"       => "Changed",
    "docs"          => "Changed",
    "config"        => "Changed",
    _               => "Changed",
  }
}

def register-changelog [data: record, changelog: string] {
  let section = (changelog-section ($data.change_type? | default "feature"))

  let detail = ($data.detail? | default "")
  let detail_block = if ($detail | str length) > 0 {
    let indented = ($detail | str trim | lines | each { |l| $"  ($l)" } | str join "\n")
    $"\n($indented)"
  } else {
    ""
  }

  let entry = $"- **($data.summary)**($detail_block)"

  let content = open $changelog
  let updated = if ($content | str contains $"### ($section)") {
    $content | str replace $"### ($section)" $"### ($section)\n\n($entry)"
  } else {
    $content | str replace "## [Unreleased]" $"## [Unreleased]\n\n### ($section)\n\n($entry)"
  }
  $updated | save --force $changelog
  print $"  changelog: ($section) — ($data.summary)"
}

def register-ontology-state [root: string, dim_id: string, new_state: string] {
  let state_file = $"($root)/.ontology/state.ncl"
  if not ($state_file | path exists) {
    print $"  skip   : ($state_file) not found"
    return
  }

  let state_data = (daemon-export-safe $state_file)
  if $state_data == null {
    print $"  error  : cannot export ($state_file)"
    return
  }

  let dims = ($state_data | get dimensions)
  if not ($dims | any { |d| $d.id == $dim_id }) {
    print $"  skip   : dimension '($dim_id)' not found in state.ncl"
    return
  }

  # sed range: from line matching id = "dim_id" to the closing brace of that block,
  # replace the first occurrence of current_state = "...".
  # macOS sed uses '' for -i; GNU sed uses -i without arg — try macOS first.
  let sed_script = $"/id.*=.*\"($dim_id)\"/,/},/ s/current_state[[:space:]]*=[[:space:]]*\"[^\"]*\"/current_state  = \"($new_state)\"/"
  let sed_result = do { ^sed -i '' $sed_script $state_file } | complete
  let sed_result = if $sed_result.exit_code != 0 {
    do { ^sed -i $sed_script $state_file } | complete
  } else {
    $sed_result
  }

  if $sed_result.exit_code != 0 {
    print $"  error  : sed patch failed — edit ($state_file) manually"
    return
  }

  let typecheck = do { ^nickel typecheck $state_file } | complete
  if $typecheck.exit_code != 0 {
    print $"  error  : typecheck failed after patch — reverting"
    print $typecheck.stderr
    do { ^git checkout -- $state_file } | complete | ignore
    return
  }
  print $"  ontology: ($dim_id) → ($new_state)"
}

def register-mode-stub [root: string, mode_id: string, summary: string] {
  let modes_dir = $"($root)/reflection/modes"
  let mode_file = $"($modes_dir)/($mode_id).ncl"

  if ($mode_file | path exists) {
    print $"  skip   : ($mode_id).ncl already exists — edit manually"
    return
  }

  if not ($modes_dir | path exists) {
    ^mkdir -p $modes_dir
  }

  let header = "let s = import \"../schema.ncl\" in\n\n"
  let body   = $"{
  id      = \"($mode_id)\",
  trigger = \"($summary)\",

  preconditions = [],

  steps = [
    {
      id       = \"step-1\",
      action   = \"describe the action\",
      actor    = 'Both,
      cmd      = \"\",
      verify   = \"\",
      on_error = { strategy = 'Stop },
    },
  ],

  postconditions = [],
} | s.Mode std.string.NonEmpty\n"
  let stub = $header + $body
  $stub | save --force $mode_file
  print $"  mode   : stub written at reflection/modes/($mode_id).ncl — fill steps"
}
