# -*- coding: utf-8 -*-
"""
build_registers.py — C2 REGISTER PROJECTOR.

Projects project_data.py into:
  03_Registers/Project_Registers_v2.0.xlsx   (one typed sheet per register, GOV-F5.2)
  03_Registers/<NAME>.csv            (plain-text projection, GOV-F9.8)

The RTM and the Closure Log are GENERATED here, never hand-maintained (GOV-B5.2, GOV-H2.2).
"""
import os, csv, datetime
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
import project_data as P

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
REG  = os.path.join(ROOT, "03_Registers")
os.makedirs(REG, exist_ok=True)

HDR_FILL = PatternFill("solid", fgColor="1B2A38")
HDR_FONT = Font(name="Arial", size=10, bold=True, color="FFFFFF")
BODY     = Font(name="Arial", size=10)
BOLD     = Font(name="Arial", size=10, bold=True)
ALT      = PatternFill("solid", fgColor="F2F6F8")
THIN     = Side(style="thin", color="D5DEE4")
BOX      = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)


# --------------------------------------------------------------- generated registers
def rtm():
    """GOV-B5.2 — generated Requirements Traceability Matrix."""
    comp_for = {}
    for cid, name, resp, inp, outp, owner, reqs in P.COMPONENTS:
        for r in reqs:
            comp_for.setdefault(r, []).append("%s %s" % (cid, name))
    evd_for = {}
    for eid, req, method, artefact, date, by in P.EVD:
        evd_for.setdefault(req, []).append("%s (%s, verified by %s)" % (eid, method, by))
    obj_for = {
        "REQ-SYS-01": "OBJ-1", "REQ-SYS-06": "OBJ-1", "REQ-SYS-11": "OBJ-1", "REQ-SYS-18": "OBJ-1",
        "REQ-SYS-05": "OBJ-2", "REQ-SYS-07": "OBJ-2", "REQ-SYS-10": "OBJ-2",
        "REQ-SYS-03": "OBJ-3", "REQ-SYS-04": "OBJ-3", "REQ-SYS-08": "OBJ-3", "REQ-SYS-09": "OBJ-3",
        "REQ-SYS-12": "OBJ-4",
        "REQ-SYS-13": "OBJ-5", "REQ-SYS-14": "OBJ-5", "REQ-SYS-15": "OBJ-5", "REQ-SYS-16": "OBJ-5",
        "REQ-SYS-17": "OBJ-5", "REQ-CON-01": "OBJ-5", "REQ-CON-02": "OBJ-1", "REQ-CON-03": "OBJ-5",
        "REQ-CON-04": "OBJ-5", "REQ-CON-05": "OBJ-1", "REQ-CON-06": "OBJ-5",
        "REQ-MOE-01": "OBJ-3", "REQ-MOP-01": "OBJ-5",
    }
    rows = []
    for r in P.REQ:
        rid = r[0]
        rows.append([
            "Purpose", obj_for.get(rid, "UNALLOCATED"), rid,
            "; ".join(comp_for.get(rid, [])) or "UNALLOCATED",
            r[5], r[7], "; ".join(evd_for.get(rid, [])) or "NO EVIDENCE",
        ])
    return rows

def closure_log():
    """GOV-H2.2 — generated projection of every register that must reach a terminal state."""
    rows = []
    def add(t, i, what, owner, raised, due, blocking, status, cond):
        rows.append([t, i, what, owner, raised, due, blocking, status, cond])
    for r in P.RQ:
        add("Request", r[0], r[2][:110], "Master Brain", r[1], r[1], "-", r[5],
            "The thing Zaid asked for exists and is delivered")
    for a in P.ACT:
        add("Action owed by Zaid", a[0], a[1], "Zaid", a[3], a[4], a[5], a[6],
            "Zaid has provided the input or made the decision")
    for c in P.CR:
        add("Change", c[0], c[3][:110], "Master Brain", c[1], c[1], "-", c[13],
            "Implemented, verified and synchronised")
    for d in P.DEF:
        add("Defect", d[0], d[1][:110], d[5], P.CURRENCY_DATE, P.CURRENCY_DATE, "-", d[8],
            "Corrective action done AND independently verified")
    for r in P.RSK:
        add("Risk", r[0], r[1][:110], r[7], P.CURRENCY_DATE, "2026-09-20",
            "Capital decision", r[8], "Mitigated, or accepted by Zaid in writing")
    for i in P.ISS:
        add("Issue", i[0], i[1][:110], i[5], P.CURRENCY_DATE, "2026-09-20", "Confidence level", i[6],
            "Resolved or accepted by Zaid")
    for b in P.BKL:
        add("Backlog", b[0], b[1][:110], b[6], P.CURRENCY_DATE, "-", b[3], b[7],
            "Done, or explicitly dropped with a reason")
    for d in P.DEC:
        add("Decision", d[0], d[1][:110], "Zaid", d[7], d[7], "-", d[8],
            "Recorded with alternatives, weights, scoring and sensitivity")
    return rows


# --------------------------------------------------------------- sheet definitions
SHEETS = [
 ("REQ", ["ID","Statement","Rationale","Source","Acceptance criteria","Verification method",
          "Owner","Status","Type","Priority","Baseline"], P.REQ),
 ("SRC", ["ID","Claim","Value","Source URL","Publisher","Source date","Accessed","Confidence",
          "Why below High"], P.SRC),
 ("ASM", ["ID","Assumption","Why it was needed","Confidence","What breaks if it is wrong",
          "Who can confirm it","Status"], P.ASM),
 ("RQ",  ["ID","Date","Zaid's words, verbatim and unedited","The interpretation acted on",
          "Outcome","Status","% complete"], P.RQ),
 ("ACT", ["ID","Action owed by Zaid","Why it matters","Raised","Due","What it is blocking","Status"], P.ACT),
 ("DEC", ["ID","Decision","Alternatives considered","Criteria and weights (fixed before scoring)",
          "Scoring","Sensitivity","Rationale","Date","Status"], P.DEC),
 ("RSK", ["ID","Risk","Cause","Impact","Likelihood 1-5","Consequence 1-5","Rating","Mitigation",
          "Owner","Status"], [(r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9]) for r in P.RSK]),
 ("ISS", ["ID","Issue","Cause","Impact","Mitigation","Owner","Status"], P.ISS),
 ("DEF", ["ID","What was defective","Rule breached","Severity","Cause","Owner",
          "Corrective action","Verification of the fix","Status"], P.DEF),
 ("IF",  ["ID","Endpoint A","Endpoint B","Direction","Item exchanged","Format","Trigger",
          "Owner A","Owner B"], P.IF_REG),
 ("CR",  ["ID","Date","Initiated by","What changed","Why","Class and deciding question",
          "Affected REQ IDs","Affected interfaces","Approval","V&V and regression evidence",
          "Sync check result","Archive snapshot","Version","Status"], P.CR),
 ("BKL", ["ID","What","Why it matters","REQ served","Priority","Effort","Owner","Status"], P.BKL),
 ("WVR", ["ID","Rule waived","Reason","Risk accepted","Compensating control","Approver","Expiry"], P.WVR),
 ("CI",  ["ID","Configuration item","Owner","Version","Location (relative to project root)","Baseline"], P.CI),
 ("STD", ["ID","Standard or code","Edition","Clauses that apply","Compliance evidence",
          "Currency last verified"], P.STD),
 ("RUN", ["ID","Date","Model","Platform","Tools and connectors","Standard version",
          "Accepted by","Note"], P.RUN),
 ("SITE",["ID","Date","Domain","Why (task and REQ)","What was done","What data it saw",
          "Approval gate hit","Outcome"], P.SITE),
 ("FEEDBACK", ["ID","Date","Correction or preference","Reason class","How it was applied"], P.FEEDBACK),
 ("COMPONENTS", ["ID","Component","Single responsibility","Inputs","Outputs","Owner","Requirements scoped to it"],
   [(c[0],c[1],c[2],c[3],c[4],c[5],", ".join(c[6])) for c in P.COMPONENTS]),
 ("AGENTS", ["ID","Role","Mission","REQ IDs it must satisfy","Inputs","Expected output (artefact + path)",
             "Prohibitions","Exit gate"], P.AGENTS),
 ("RACI", ["Artefact","Built by","Verified by","Validated by"], P.RACI),
 ("EVD", ["ID","REQ ID proven","Method","Evidence artefact path","Date","Produced by"], P.EVD),
 ("FAH", ["FUN-ID","Function","Deliverable / location","ACT-ID","Action (user does...)","Control",
          "HLP-ID","Help entry title","Diataxis type","Help link","Back link","Owner",
          "Version / last verified","Status"], P.FAH),
 ("METRIC_LINEAGE", ["Metric","What this number means","Formula in words","Source data",
                     "As at","Owner"], P.METRIC_LINEAGE),
 ("GLOSSARY", ["Term","Definition"], P.GLOSSARY),
 ("RTM", ["Purpose","Objective","Requirement","Component","Verification method","Status","Evidence"], None),
 ("CLOSURE_LOG", ["Type","ID","What","Owner","Raised","Due","Blocking","Status","Closure condition"], None),
]

WIDTHS = {
 "ID": 14, "Statement": 62, "Rationale": 42, "Source": 24, "Acceptance criteria": 60,
 "Claim": 46, "Value": 46, "Source URL": 52, "Assumption": 56, "What breaks if it is wrong": 60,
 "Zaid's words, verbatim and unedited": 70, "The interpretation acted on": 70, "Outcome": 40,
 "Action owed by Zaid": 56, "Why it matters": 66, "Decision": 52, "Alternatives considered": 70,
 "Criteria and weights (fixed before scoring)": 60, "Scoring": 40, "Sensitivity": 66, "Rationale": 60,
 "Risk": 62, "Cause": 34, "Impact": 62, "Mitigation": 62, "What was defective": 58,
 "Corrective action": 60, "Verification of the fix": 46, "What changed": 60, "Why": 46,
 "Item exchanged": 34, "What": 60, "Definition": 92, "Function": 34, "Action (user does...)": 34,
 "Evidence artefact path": 62, "Evidence": 62, "Formula in words": 62, "Mission": 50,
 "Prohibitions": 60, "Exit gate": 50, "Inputs": 30, "Outputs": 34, "Expected output (artefact + path)": 40,
 "Requirements scoped to it": 40, "Compliance evidence": 30, "What it is blocking": 40,
 "Closure condition": 46, "What was done": 40, "Why (task and REQ)": 40,
}


def write_sheet(ws, headers, rows, title_note):
    # REQ-CON-04 as amended: a frozen HEADER ROW alone still loses the row label when a wide
    # register is scrolled sideways on a phone. Freezing the ID column as well is what the
    # rule actually asks for (DEF-051).
    ws.freeze_panes = "B3"
    ws.cell(row=1, column=1, value=title_note).font = Font(name="Arial", size=10, italic=True, color="55636E")
    ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=max(len(headers), 1))
    for c, h in enumerate(headers, start=1):
        cell = ws.cell(row=2, column=c, value=h)
        cell.fill, cell.font = HDR_FILL, HDR_FONT
        cell.alignment = Alignment(vertical="center", wrap_text=True)
        ws.column_dimensions[get_column_letter(c)].width = WIDTHS.get(h, 20)
    ws.row_dimensions[2].height = 30
    for r, row in enumerate(rows, start=3):
        for c, val in enumerate(row, start=1):
            cell = ws.cell(row=r, column=c, value=val)
            cell.font = BOLD if c == 1 else BODY
            cell.alignment = Alignment(vertical="top", wrap_text=True)
            cell.border = BOX
            if r % 2 == 1:
                cell.fill = ALT
    ws.auto_filter.ref = "A2:%s%d" % (get_column_letter(len(headers)), max(2, len(rows) + 2))


def main():
    wb = Workbook()
    wb.remove(wb.active)

    idx = wb.create_sheet("INDEX")
    idx.column_dimensions["A"].width = 22
    idx.column_dimensions["B"].width = 96
    idx.cell(row=1, column=1, value="%s — REGISTER SET  %s" % (P.PROJECT_NAME, P.BASELINE_VERSION)).font = \
        Font(name="Arial", size=14, bold=True, color="1B2A38")
    idx.cell(row=2, column=1,
             value=("GENERATED FILE. Every sheet in this workbook is a projection of "
                    "01_System/project_data.py (GOV-B3.2, GOV-F2.2). Do not hand-edit it — "
                    "your edit will be overwritten on the next build. Edit project_data.py and "
                    "re-run 01_System/build_all.py.")).font = Font(name="Arial", size=10, italic=True, color="B03A2E")
    idx.merge_cells("A2:B2")
    idx.cell(row=3, column=1, value="Project class: %s   Jurisdiction: %s   All facts current at: %s"
             % (P.PROJECT_CLASS, P.JURISDICTION, P.CURRENCY_DATE)).font = BODY
    idx.merge_cells("A3:B3")
    descriptions = {
     "REQ":"What is required. Eleven fields, exactly one 'shall', a pass/fail acceptance criterion. The single source of truth.",
     "SRC":"Every external fact in this project, with its URL, publisher, date and confidence. The anti-fabrication spine.",
     "ASM":"Everything the study relies on that is NOT proven, with what breaks if it is wrong.",
     "RQ":"Zaid's requests, in his own words, next to the interpretation the AI acted on.",
     "ACT":"Things only Zaid can do. Each says what it unblocks.",
     "DEC":"Decisions, with alternatives, weights fixed before scoring, and a sensitivity statement.",
     "RSK":"Risks scored against criteria set before the first risk was logged.",
     "ISS":"Risks that have already happened.",
     "DEF":"Defects. Anything the governance standard calls defective lands here.",
     "IF":"Interfaces between components. Both endpoints, both owners.",
     "CR":"Change records. Eight-state lifecycle, class and deciding question recorded.",
     "BKL":"Identified but not yet done.",
     "WVR":"Waivers. Empty — none were raised, and the AI cannot self-grant one.",
     "CI":"Configuration items: every controlled file with its version and location.",
     "STD":"External standards and codes cited, with edition and currency date.",
     "RUN":"AI run record: what produced these artefacts, on what platform, under which standard.",
     "SITE":"Every domain reached, why, and what it saw. Zero banking or payment domains.",
     "FEEDBACK":"Corrections and standing preferences from Zaid, and how they were applied.",
     "COMPONENTS":"The nine components, each with exactly one responsibility.",
     "AGENTS":"Sub-agent charters. Six mandatory fields each.",
     "RACI":"Proof that the builder of a thing is never its verifier.",
     "EVD":"Evidence set. The trace chain terminates here.",
     "FAH":"Function to action to help entry. Fourteen fixed columns.",
     "METRIC_LINEAGE":"What every number on the dashboard means and where it comes from.",
     "GLOSSARY":"Every abbreviation used anywhere in this project.",
     "RTM":"GENERATED traceability matrix: purpose to objective to requirement to component to evidence.",
     "CLOSURE_LOG":"GENERATED. Everything that must reach a terminal state, in one list.",
    }
    idx.cell(row=5, column=1, value="Sheet").font = HDR_FONT
    idx.cell(row=5, column=1).fill = HDR_FILL
    idx.cell(row=5, column=2, value="What it holds").font = HDR_FONT
    idx.cell(row=5, column=2).fill = HDR_FILL

    r = 6
    for name, headers, rows in SHEETS:
        data = rows if rows is not None else (rtm() if name == "RTM" else closure_log())
        ws = wb.create_sheet(name)
        note = ("GENERATED PROJECTION of 01_System/project_data.py — do not hand-edit. "
                "Project %s, baseline %s, facts current at %s."
                % (P.PROJECT_NAME, P.BASELINE_VERSION, P.CURRENCY_DATE))
        write_sheet(ws, headers, [list(x) for x in data], note)
        idx.cell(row=r, column=1, value=name).font = BOLD
        idx.cell(row=r, column=2, value="%s  (%d rows)" % (descriptions.get(name, ""), len(data))).font = BODY
        idx.cell(row=r, column=2).alignment = Alignment(wrap_text=True, vertical="top")
        r += 1

        # plain-text projection — GOV-F9.8
        with open(os.path.join(REG, name + ".csv"), "w", newline="", encoding="utf-8") as f:
            w = csv.writer(f)
            w.writerow(headers)
            for row in data:
                w.writerow(list(row))

    out = os.path.join(REG, "Project_Registers_v2.0.xlsx")
    wb.save(out)
    print("wrote", out)
    print("wrote %d csv projections into %s" % (len(SHEETS), REG))


if __name__ == "__main__":
    main()
