# -*- coding: utf-8 -*-
"""D1 §10 JS element-ID integrity gate.

For every template under internal/web/templates and internal/setup/templates, extract each
getElementById('X') / querySelector('#X') literal used by the file's inline JS and assert an
id="X" exists in the SAME file — or that the ID is created by that file's own JS (innerHTML /
insertAdjacentHTML string containing id="X" / id='X'), or is explicitly allowlisted below with
a justification.

Exit 1 on any unresolved reference. Run from the repo's controller/ directory:
    python scripts/template_id_gate.py
"""
import io, os, re, sys

ROOTS = [
    os.path.join("internal", "web", "templates"),
    os.path.join("internal", "setup", "templates"),
]

# Dynamic-ID exceptions: (template, id-prefix-or-name) -> justification.
# Suffix-parameterized IDs (id + variable) are handled generically below; these are the rest.
ALLOW = {
    # layout.html builds the alert/delete/remove modals entirely in JS and later looks them up.
    ("layout.html", "alert-modal"): "created by showAlert() via innerHTML in the same file",
    ("layout.html", "delete-modal"): "created by deleteOrphanStack() via innerHTML",
    ("layout.html", "remove-modal"): "created by removeStack() via innerHTML",
    ("layout.html", "confirm-delete-btn"): "created inside the delete-modal innerHTML",
    ("layout.html", "confirm-remove-btn"): "created inside the remove-modal innerHTML",
    ("layout.html", "delete-hdd-check"): "created inside the delete-modal innerHTML",
    ("layout.html", "remove-hdd-check"): "created inside the remove-modal innerHTML",
    ("layout.html", "remove-backup-check"): "created inside the remove-modal innerHTML",
    ("layout.html", "remove-hdd-keep-warning"): "created inside the remove-modal innerHTML",
    ("layout.html", "sync-btn"): "lives on stacks.html; syncTemplates() is shared layout JS guarded by if(!btn)return",
    ("layout.html", "sync-toast"): "lives on stacks.html; guarded null-check",
}

GET_RE = re.compile(r"getElementById\(\s*['\"]([A-Za-z0-9_-]+)['\"]\s*\)")
GET_DYN_RE = re.compile(r"getElementById\(\s*['\"]([A-Za-z0-9_-]+)['\"]\s*\+")
QS_RE = re.compile(r"querySelector\(\s*['\"]#([A-Za-z0-9_-]+)['\"]\s*\)")
ID_ATTR_RE = re.compile(r"""id=["']([A-Za-z0-9_{}\. $-]+)["']""")
ID_IN_JS_RE = re.compile(r"""id=\\?["']([A-Za-z0-9_-]+)\\?["']""")


def check(path):
    fname = os.path.basename(path)
    src = io.open(path, encoding="utf-8").read()
    static_refs = set(GET_RE.findall(src)) | set(QS_RE.findall(src))
    dyn_prefixes = set(GET_DYN_RE.findall(src))
    # static refs regex also matches the dynamic form's literal — subtract prefixes used with '+'
    static_refs -= dyn_prefixes
    defined = set(ID_ATTR_RE.findall(src)) | set(ID_IN_JS_RE.findall(src))
    defined_prefixes = tuple(d.split("{{")[0] for d in defined if "{{" in d or d.endswith("-"))

    problems = []
    for ref in sorted(static_refs):
        if ref in defined:
            continue
        # a template-parameterized id like id="field-{{.EnvVar}}" legitimately renders
        # ids such as field-SUBDOMAIN — match static refs against those prefixes
        if defined_prefixes and ref.startswith(defined_prefixes):
            continue
        if (fname, ref) in ALLOW:
            continue
        problems.append("static #%s not defined in %s" % (ref, fname))
    for pref in sorted(dyn_prefixes):
        # a dynamic lookup 'x-' + var needs SOME id starting with that prefix (template- or JS-created)
        if any(d.startswith(pref) for d in defined) or pref in defined_prefixes:
            continue
        if (fname, pref) in ALLOW:
            continue
        problems.append("dynamic prefix #%s* not defined in %s" % (pref, fname))
    return problems


def main():
    bad = []
    for root in ROOTS:
        for fn in sorted(os.listdir(root)):
            if not fn.endswith(".html"):
                continue
            bad += check(os.path.join(root, fn))
    if bad:
        print("INTEGRITY GATE FAILED (%d):" % len(bad))
        for b in bad:
            print("  -", b)
        sys.exit(1)
    print("integrity gate OK — every JS element-ID reference resolves within its own template")


if __name__ == "__main__":
    main()
