#!/usr/bin/env nu
# Validate that reflection/forms/config.ncl and reflection/forms/config.ncl.j2
# are in sync: every form field with a nickel_path must have a {{ name }} reference
# in the template, and every {{ var }} in the template must map to a known form field.
#
# Run:  nu install/check-config-sync.nu
# CI:   just ci-check-config-sync

let form_path     = "reflection/forms/config.ncl"
let template_path = "reflection/forms/config.ncl.j2"

# ── Extract field names that have nickel_path ─────────────────────────────────
# Strategy: scan the form for blocks containing both `name = "..."` and `nickel_path`.
# We do a two-pass: first collect all (line_number, name) pairs, then check which
# have a nickel_path within the same element block (lines up to the next `}`).

let form_lines = open --raw $form_path | lines | enumerate

# Collect all element start positions and their names
let elements = $form_lines | reduce --fold [] {|row, acc|
    let line = $row.item
    if ($line | str contains "name = \"") {
        let name = try {
            $line | parse --regex 'name\s*=\s*"(?P<n>[^"]+)"' | get n | first
        } catch { "" }
        if ($name | is-not-empty) {
            $acc | append { idx: $row.index, name: $name }
        } else {
            $acc
        }
    } else {
        $acc
    }
}

# For each element, check if its block (from its line until the next element or EOF)
# contains a nickel_path. Collect names that have nickel_path.
let form_text = open --raw $form_path

let ncl_path_fields = $elements | reduce --fold [] {|el, acc|
    # Find the substring from this element's line forward to end, check for nickel_path
    # before the next `name =` (other than within nested structures)
    let after = $form_lines
        | skip $el.idx
        | take while {|r| not ($r.item | str contains "name = \"") or $r.index == $el.idx }
        | get item
        | str join "\n"
    if ($after | str contains "nickel_path") {
        $acc | append $el.name
    } else {
        $acc
    }
}

# ── Extract {{ var }} references from template ────────────────────────────────
let template_vars = open --raw $template_path
    | parse --regex '\{\{\s*(?P<var>[a-zA-Z_][a-zA-Z0-9_]*)\s*[\|}\s]'
    | get var
    | uniq
    | where {|v| $v != "if" and $v != "else" and $v != "endif" and $v != "for" and $v != "endfor"}

# ── Cross-check ───────────────────────────────────────────────────────────────
mut errors = []

# 1. Every nickel_path field must appear in template
for field in $ncl_path_fields {
    if not ($template_vars | any {|v| $v == $field}) {
        $errors = ($errors | append $"MISSING in template: form field '($field)' has nickel_path but no \{\{ ($field) \}\} in config.ncl.j2")
    }
}

# 2. Every template var must exist as a form field name (excluding Tera filters/functions)
let all_form_names = $elements | get name
let tera_builtins = ["json", "trim", "split", "map", "attribute", "pat"]

for var in $template_vars {
    if not ($all_form_names | any {|n| $n == $var}) and not ($tera_builtins | any {|b| $b == $var}) {
        $errors = ($errors | append $"ORPHAN in template: \{\{ ($var) \}\} has no matching form field in config.ncl")
    }
}

# ── Report ────────────────────────────────────────────────────────────────────
if ($errors | is-empty) {
    print $"(ansi green)✓ config form/template in sync(ansi reset)  ($ncl_path_fields | length) fields, ($template_vars | length) template vars"
} else {
    print $"(ansi red)✗ config form/template DRIFT DETECTED(ansi reset)"
    for e in $errors {
        print $"  (ansi yellow)→(ansi reset) ($e)"
    }
    print ""
    print "Fix: update reflection/forms/config.ncl AND reflection/forms/config.ncl.j2 together."
    exit 1
}
