#!/usr/bin/env nu
# install/install.nu — ontoref-daemon installer
#
# Installed layout:
#
#   ~/.local/bin/
#     ontoref              ← global CLI wrapper (install/ontoref-global)
#     ontoref-daemon       ← bootstrapper: nickel export | ontoref-daemon.bin (ADR-004)
#     ontoref-daemon.bin   ← compiled binary (not called directly)
#
# Platform data/config:
#   macOS:
#     data    ~/Library/Application Support/ontoref/   templates, public, nulib
#     config  ~/.config/ontoref/
#   Linux:
#     data    ~/.local/share/ontoref/
#     config  ~/.config/ontoref/
#
# Dev mode   (Cargo.toml present + nu in PATH): also installs ncl-bootstrap Nu helper
# Service mode: bootstrapper is the sole entrypoint — .bin never called directly

def install-if-changed [src: string, dest: string, label: string] {
  let needs_update = if ($dest | path exists) {
    (open --raw $src | hash sha256) != (open --raw $dest | hash sha256)
  } else {
    true
  }
  if $needs_update {
    cp $src $dest
    chmod +x $dest
    print $"✓ ($label)  ($dest)"
  } else {
    print $"— ($label)  unchanged"
  }
}

def main [] {
  let repo_root = $env.PWD
  let is_dev    = (("Cargo.toml" | path exists) and ((which nu | length) > 0))

  let platform = (sys host | get name)
  let is_mac   = ((^uname) == "Darwin")

  let data_dir   = if $is_mac {
    $"($env.HOME)/Library/Application Support/ontoref"
  } else {
    $"($env.HOME)/.local/share/ontoref"
  }
  let config_dir = $"($env.HOME)/.config/ontoref"
  let bin_dir    = $"($env.HOME)/.local/bin"

  mkdir $bin_dir

  # ── 1. Binary → ontoref-daemon.bin ────────────────────────────────────────
  let bin_src  = $"($repo_root)/target/release/ontoref-daemon"
  let bin_dest = $"($bin_dir)/ontoref-daemon.bin"

  if not ($bin_src | path exists) {
    error make { msg: $"binary not found: ($bin_src)\n  run: cargo build --release -p ontoref-daemon" }
  }

  # Stop any running instance — macOS kills the new process if the old one holds the file
  do { ^pkill -x ontoref-daemon.bin } | ignore
  do { ^pkill -x ontoref-daemon } | ignore

  cp $bin_src $bin_dest
  chmod +x $bin_dest

  #if $is_mac {
  #  do { ^xattr -d com.apple.quarantine $bin_dest } | ignore
  #}

  print $"✓ binary         ($bin_dest)"

  # ── 2. Bootstrapper → ontoref-daemon ──────────────────────────────────────
  # The bootstrapper IS the public entrypoint. Users call ontoref-daemon, never .bin directly.
  let boot_src  = $"($repo_root)/install/ontoref-daemon-boot"
  let boot_dest = $"($bin_dir)/ontoref-daemon"

  install-if-changed $boot_src $boot_dest "bootstrapper"

  # ── 3. Global CLI wrapper → ontoref ───────────────────────────────────────
  # Bake the data dir as ONTOREF_ROOT so the installed wrapper is self-contained
  # and does not require the source repo to be present at runtime.
  let cli_src    = $"($repo_root)/install/ontoref-global"
  let cli_dest   = $"($bin_dir)/ontoref"
  let cli_baked  = (
    open --raw $cli_src
    | str replace 'ONTOREF_ROOT="${ONTOREF_ROOT:-ontoref}"' $'ONTOREF_ROOT="${ONTOREF_ROOT:-($data_dir)}"'
  )

  let needs_update = if ($cli_dest | path exists) {
    ($cli_baked | hash sha256) != (open --raw $cli_dest | hash sha256)
  } else {
    true
  }

  if $needs_update {
    $cli_baked | save --force $cli_dest
    chmod +x $cli_dest
    print $"✓ cli         ($cli_dest)"
  } else {
    print $"— cli         unchanged"
  }

  # ── 3b. Reflection scripts (data dir) ─────────────────────────────────────
  # The global CLI wrapper calls $data_dir/reflection/bin/ontoref.nu directly.
  # Copy the entire reflection/ tree so the install is autonomous (no dev repo needed).
  let reflection_src  = $"($repo_root)/reflection"
  let reflection_dest = $"($data_dir)/reflection"

  if not ($reflection_src | path exists) {
    error make { msg: $"reflection/ not found: ($reflection_src)" }
  }

  mkdir $reflection_dest
  mut refl_updated = 0
  mut refl_skipped = 0
  for src_file in (glob $"($reflection_src)/**/*" | where { |f| ($f | path type) == "file" }) {
    let rel        = ($src_file | str replace $"($reflection_src)/" "")
    let dest_file  = $"($reflection_dest)/($rel)"
    let dest_parent = ($dest_file | path dirname)
    mkdir $dest_parent
    let needs_update = if ($dest_file | path exists) {
      (open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
    } else {
      true
    }
    if $needs_update {
      cp $src_file $dest_file
      $refl_updated = $refl_updated + 1
    } else {
      $refl_skipped = $refl_skipped + 1
    }
  }
  print $"✓ reflection     ($reflection_dest)/  updated=($refl_updated) unchanged=($refl_skipped)"

  # ── 3c. Domain extensions (domains/) ─────────────────────────────────────
  # Domain commands are dispatched by the bash wrapper at runtime from $ONTOREF_ROOT/domains/.
  let domains_src  = $"($repo_root)/domains"
  let domains_dest = $"($data_dir)/domains"

  if ($domains_src | path exists) {
    mkdir $domains_dest
    mut dom_updated = 0
    mut dom_skipped = 0
    for src_file in (glob $"($domains_src)/**/*" | where { |f| ($f | path type) == "file" }) {
      let rel         = ($src_file | str replace $"($domains_src)/" "")
      let dest_file   = $"($domains_dest)/($rel)"
      let dest_parent = ($dest_file | path dirname)
      mkdir $dest_parent
      let needs_update = if ($dest_file | path exists) {
        (open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
      } else {
        true
      }
      if $needs_update {
        cp $src_file $dest_file
        $dom_updated = $dom_updated + 1
      } else {
        $dom_skipped = $dom_skipped + 1
      }
    }
    print $"✓ domains        ($domains_dest)/  updated=($dom_updated) unchanged=($dom_skipped)"

    # Build aliases.txt and install short_alias bin wrappers for each domain.
    mut alias_lines = []
    for domain_dir in (ls $domains_src | where type == "dir" | get name) {
      let domain_ncl = $"($domain_dir)/domain.ncl"
      if not ($domain_ncl | path exists) { continue }
      let domain_data = (do { ^nickel export $domain_ncl } | complete)
      if $domain_data.exit_code != 0 { continue }
      let d = ($domain_data.stdout | from json)
      let alias = ($d.short_alias? | default "")
      let domain_id = ($d.id? | default "")
      if ($alias | is-empty) { continue }

      # aliases.txt entry — only when alias differs from domain id (e.g. prov → provisioning)
      # skipped when alias == domain_id (e.g. personal → personal): ore already handles it natively
      if $alias != $domain_id {
        $alias_lines = ($alias_lines | append $"($alias)=($domain_id)")
      }

      # standalone bin wrapper — always created when short_alias is set,
      # even when alias == domain_id, so `personal state` works without `ore`
      let alias_dest = $"($bin_dir)/($alias)"
      let alias_body = $"#!/bin/bash\nexec ontoref ($domain_id) \"$@\"\n"
      let needs_update = if ($alias_dest | path exists) {
        ($alias_body | hash sha256) != (open --raw $alias_dest | hash sha256)
      } else { true }
      if $needs_update {
        $alias_body | save --force $alias_dest
        chmod +x $alias_dest
        print $"✓ alias          ($alias_dest)  → ontoref ($domain_id)"
      } else {
        print $"— alias          ($alias) unchanged"
      }
    }

    # Write consolidated aliases.txt to the installed domains dir
    let aliases_dest = $"($domains_dest)/aliases.txt"
    if ($alias_lines | is-not-empty) {
      let aliases_body = ($alias_lines | str join "\n")
      let needs_update = if ($aliases_dest | path exists) {
        ($aliases_body | hash sha256) != (open --raw $aliases_dest | hash sha256)
      } else { true }
      if $needs_update {
        $aliases_body | save --force $aliases_dest
        print $"✓ domain-aliases ($aliases_dest)"
      } else {
        print $"— domain-aliases unchanged"
      }
    }
  } else {
    print $"  (ansi yellow)warn(ansi reset) domains/ not found at ($domains_src)"
  }

  # ── 3d. CLI templates (project.ncl, ontoref-config.ncl, ontology/ stubs) ──
  # `ontoref setup` reads from $ONTOREF_ROOT/templates/ — copy the repo-level
  # templates/ tree so the installed CLI works without the source repo present.
  let cli_templates_src  = $"($repo_root)/templates"
  let cli_templates_dest = $"($data_dir)/templates"

  if ($cli_templates_src | path exists) {
    mkdir $cli_templates_dest
    mut tmpl_updated = 0
    mut tmpl_skipped = 0
    for src_file in (glob $"($cli_templates_src)/**/*" | where { |f| ($f | path type) == "file" }) {
      let rel        = ($src_file | str replace $"($cli_templates_src)/" "")
      let dest_file  = $"($cli_templates_dest)/($rel)"
      let dest_parent = ($dest_file | path dirname)
      mkdir $dest_parent
      let needs_update = if ($dest_file | path exists) {
        (open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
      } else {
        true
      }
      if $needs_update {
        cp $src_file $dest_file
        $tmpl_updated = $tmpl_updated + 1
      } else {
        $tmpl_skipped = $tmpl_skipped + 1
      }
    }
    print $"✓ cli-templates  ($cli_templates_dest)/  updated=($tmpl_updated) unchanged=($tmpl_skipped)"
  } else {
    print $"  (ansi yellow)warn(ansi reset) templates/ not found at ($cli_templates_src)"
  }

  # ── 3e. Ontology defaults + schemas (data dir) ───────────────────────────
  # Consumer projects import "ontology/defaults/state.ncl", "defaults/manifest.ncl", etc.
  # These must be resolvable from $ONTOREF_ROOT (the data dir).
  # Structure: $data_dir/ontology/{defaults,schemas}/
  # The bash wrapper includes $ONTOREF_ROOT in NICKEL_IMPORT_PATH, so:
  #   import "ontology/defaults/state.ncl" → $data_dir/ontology/defaults/state.ncl ✓
  #   import "defaults/state.ncl"          → $data_dir/ontology/defaults/state.ncl ✓ (via $ONTOREF_ROOT/ontology in nickel-import-path)
  let ontology_src  = $"($repo_root)/ontology"
  let ontology_dest = $"($data_dir)/ontology"
  if ($ontology_src | path exists) {
    mkdir $ontology_dest
    mut ont_updated = 0
    mut ont_skipped = 0
    for src_file in (glob $"($ontology_src)/**/*.ncl" | where { |f| ($f | path type) == "file" }) {
      let rel        = ($src_file | str replace $"($ontology_src)/" "")
      let dest_file  = $"($ontology_dest)/($rel)"
      let dest_parent = ($dest_file | path dirname)
      mkdir $dest_parent
      let needs_update = if ($dest_file | path exists) {
        (open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
      } else {
        true
      }
      if $needs_update { cp $src_file $dest_file; $ont_updated = $ont_updated + 1 } else { $ont_skipped = $ont_skipped + 1 }
    }
    print $"✓ ontology       ($ontology_dest)/  updated=($ont_updated) unchanged=($ont_skipped)"
  }

  # ── 4. UI assets (data dir) ────────────────────────────────────────────────
  let templates_src = $"($repo_root)/crates/ontoref-daemon/templates"
  let public_src    = $"($repo_root)/crates/ontoref-daemon/public"

  if not ($templates_src | path exists) {
    error make { msg: $"templates not found: ($templates_src)" }
  }

  mkdir $data_dir

  let asset_dirs = [$templates_src $public_src]
  mut updated = 0
  mut skipped = 0

  for asset_dir in $asset_dirs {
    let dir_name = ($asset_dir | path basename)
    let dest_base = $"($data_dir)/($dir_name)"
    mkdir $dest_base
    for src_file in (glob $"($asset_dir)/**/*" | where { |f| ($f | path type) == "file" }) {
      let rel   = ($src_file | str replace $"($asset_dir)/" "")
      let dest_file = $"($dest_base)/($rel)"
      let dest_parent = ($dest_file | path dirname)
      mkdir $dest_parent
      let needs_update = if ($dest_file | path exists) {
        (open --raw $src_file | hash sha256) != (open --raw $dest_file | hash sha256)
      } else {
        true
      }
      if $needs_update {
        cp $src_file $dest_file
        $updated = $updated + 1
      } else {
        $skipped = $skipped + 1
      }
    }
  }

  print $"✓ assets         ($data_dir)/  updated=($updated) unchanged=($skipped)"

  # ── 5. Config skeleton + global NATS topology ─────────────────────────────
  let streams_default = $"($repo_root)/install/resources/streams.json"
  let streams_dest    = $"($config_dir)/streams.json"

  if ($streams_default | path exists) {
    mkdir $config_dir
    if not ($streams_dest | path exists) {
      cp $streams_default $streams_dest
      print $"✓ nats topology  ($streams_dest)"
    } else {
      print $"— nats topology  unchanged"
    }
  }

  let config_default = $"($repo_root)/install/resources/config.ncl"
  let config_dest    = $"($config_dir)/config.ncl"
  let config_example = $"($config_dir)/config.ncl.example"

  if ($config_default | path exists) {
    mkdir $config_dir
    cp $config_default $config_example

    if ($config_dest | path exists) {
      print $"ℹ config         already exists — not overwritten"
      print $"  example kept:  ($config_example)"
    } else {
      cp $config_default $config_dest
      print $"✓ config         ($config_dest)"
      print $"  edit with:     ontoref config-edit"
    }
  }

  # projects.ncl — local projects; populated by `ontoref project-add`
  let projects_default = $"($repo_root)/install/resources/projects.ncl"
  let projects_dest    = $"($config_dir)/projects.ncl"

  if ($projects_default | path exists) and not ($projects_dest | path exists) {
    cp $projects_default $projects_dest
    print $"✓ projects       ($projects_dest)"
  }

  # remote-projects.ncl — remote/push-only projects; populated by `ontoref project-add-remote`
  let remote_default = $"($repo_root)/install/resources/remote-projects.ncl"
  let remote_dest    = $"($config_dir)/remote-projects.ncl"

  if ($remote_default | path exists) and not ($remote_dest | path exists) {
    cp $remote_default $remote_dest
    print $"✓ remote-projects ($remote_dest)"
  }

  # schemas/ — project contract schemas imported by per-project .ontoref/project.ncl
  let schemas_src  = $"($repo_root)/install/resources/schemas"
  let schemas_dest = $"($config_dir)/schemas"

  if ($schemas_src | path exists) {
    mkdir $schemas_dest
    for f in (ls $schemas_src | get name) {
      let dest_f = $"($schemas_dest)/(($f | path basename))"
      if not ($dest_f | path exists) {
        cp $f $dest_f
        print $"✓ schema         ($dest_f)"
      }
    }
  }

  # ── 6. Install scripts (gen-projects.nu, etc.) + hooks ────────────────────
  # The bootstrapper looks for *.nu at $data_dir/install/.
  # `ontoref hooks-install` looks for install/hooks/{post-commit,post-merge}.
  let install_dest = $"($data_dir)/install"
  mkdir $install_dest
  for f in (glob $"($repo_root)/install/*.nu") {
    let dest_f = $"($install_dest)/(($f | path basename))"
    install-if-changed $f $dest_f $"install/(($f | path basename))"
  }
  let hooks_src  = $"($repo_root)/install/hooks"
  let hooks_dest = $"($install_dest)/hooks"
  if ($hooks_src | path exists) {
    mkdir $hooks_dest
    for f in (glob $"($hooks_src)/*" | where { |p| ($p | path type) == "file" }) {
      let dest_f = $"($hooks_dest)/(($f | path basename))"
      install-if-changed $f $dest_f $"install/hooks/(($f | path basename))"
    }
  }

  # ── 7. Dev extras: ncl-bootstrap Nu helper ────────────────────────────────
  if $is_dev {
    let nulib_dest = $"($data_dir)/nulib"
    mkdir $nulib_dest
    cp $"($repo_root)/reflection/nulib/bootstrap.nu" $"($nulib_dest)/bootstrap.nu"
    print $"✓ ncl-bootstrap  ($nulib_dest)/bootstrap.nu"
  }

  # ── Summary ────────────────────────────────────────────────────────────────
  let mode_tag = if $is_dev { "dev" } else { "service" }
  print $"\ninstalled  mode=($mode_tag)  platform=($platform)"
  print $"  bin     ($bin_dir)/ontoref, ontoref-daemon, ontoref-daemon.bin"
  print $"  data    ($data_dir)/  \(reflection/, domains/, templates/, ...\)"
  print $"  config  ($config_dir)/"
  print ""
  print "  next:   nu install/config-setup.nu"
}
