#!/usr/bin/env nu
# reflection/nulib/bootstrap.nu — NCL pipe bootstrap helper for development use.
#
# Implements ADR-004: validates config via nickel export and pipes JSON to a
# target process via --config-stdin. For interactive/dev use only — system
# service launchers must use scripts/ontoref-daemon-start (bash, zero deps).
#
# Usage:
#   use reflection/nulib/bootstrap.nu *
#   ncl-bootstrap "~/.config/ontoref/config.ncl" "ontoref-daemon" --port 7891
#   ncl-bootstrap "config.ncl" "myapp" --sops "secrets.enc.json"
#   ncl-bootstrap "config.ncl" "myapp" --dry-run   # print composed JSON

# Resolve NICKEL_IMPORT_PATH: config dir + platform data dir schemas.
# Prepended to any existing value so external overrides are preserved.
def nickel-import-path [ncl_file: string]: nothing -> string {
  let config_dir = ($ncl_file | path dirname | path expand)
  let data_dir = if ((sys host | get name) | str starts-with "Mac") {
    $"($env.HOME)/Library/Application Support/ontoref"
  } else {
    $"($env.HOME)/.local/share/ontoref"
  }
  let extra = $"($config_dir):($data_dir)/schemas:($data_dir)"
  let existing = ($env | get -i NICKEL_IMPORT_PATH | default "")
  if ($existing | is-empty) { $extra } else { $"($extra):($existing)" }
}

# Default NATS_STREAMS_CONFIG to ~/.config/ontoref/streams.json if not already set.
# Allows the daemon to pick up the global topology without a project-local streams.json.
def nats-streams-config [ncl_file: string]: nothing -> string {
  let existing = ($env | get -i NATS_STREAMS_CONFIG | default "")
  if ($existing | is-not-empty) { return $existing }
  let config_dir = ($ncl_file | path dirname | path expand)
  $"($config_dir)/streams.json"
}

# Validate and export a Bootstrapable NCL config to JSON.
# Fails with a structured error if the export fails — the target never starts.
def ncl-export-bootstrapable [ncl_file: string]: nothing -> string {
  if not ($ncl_file | path exists) {
    error make { msg: $"config not found: ($ncl_file)" }
  }
  let import_path = (nickel-import-path $ncl_file)
  let r = (do { with-env { NICKEL_IMPORT_PATH: $import_path } { ^nickel export --format json $ncl_file } } | complete)
  if $r.exit_code != 0 {
    error make { msg: $"config validation failed — daemon not started:\n($r.stderr | str trim)" }
  }
  $r.stdout
}

# Merge two JSON strings (struct config + secrets). Requires jq.
def json-merge [base: string, overlay: string]: nothing -> string {
  let r = (do { echo $"($base)\n($overlay)" | ^jq -s '.[0] * .[1]' } | complete)
  if $r.exit_code != 0 {
    error make { msg: $"JSON merge failed:\n($r.stderr | str trim)" }
  }
  $r.stdout
}

# Bootstrap a process from a Bootstrapable NCL config file.
#
# Stages:
#   1. nickel export <ncl_file>          → validated JSON
#   2a. sops --decrypt <sops_file>       → secret merge (if --sops)
#   2b. vault kv get <vault_path>        → secret merge (if --vault)
#   3. <command> --config-stdin [args]   → process reads JSON from stdin
export def ncl-bootstrap [
  ncl_file: string,           # Bootstrapable NCL config (source of truth)
  command: string,            # target binary to launch
  --sops: string = "",        # path to SOPS-encrypted secrets file
  --vault: string = "",       # Vault kv path for secrets
  --dry-run = false,          # print composed JSON to stdout, don't launch
  --stdin-flag: string = "--config-stdin",
  ...args: string,
]: nothing -> nothing {
  let struct_json = (ncl-export-bootstrapable $ncl_file)

  let composed = if ($sops | is-not-empty) {
    let r = (do { ^sops --decrypt $sops } | complete)
    if $r.exit_code != 0 {
      error make { msg: $"SOPS decrypt failed:\n($r.stderr | str trim)" }
    }
    json-merge $struct_json $r.stdout
  } else if ($vault | is-not-empty) {
    let r = (do { ^vault kv get -format=json $vault | ^jq '.data.data' } | complete)
    if $r.exit_code != 0 {
      error make { msg: $"Vault lookup failed:\n($r.stderr | str trim)" }
    }
    json-merge $struct_json $r.stdout
  } else {
    $struct_json
  }

  if $dry_run {
    print $composed
    return
  }

  if not (which $command | is-not-empty) {
    error make { msg: $"command not found: ($command)" }
  }

  let streams_cfg = (nats-streams-config $ncl_file)
  $composed | with-env { NATS_STREAMS_CONFIG: $streams_cfg } { ^$command $stdin_flag ...$args }
}
