from __future__ import annotations

import argparse
import csv
from pathlib import Path

from docx import Document
from openpyxl import Workbook
from openpyxl.formatting.rule import FormulaRule
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.worksheet.datavalidation import DataValidation


ROOT = Path(__file__).resolve().parents[1]
MATRIX = ROOT / "response-matrix" / "product-gap-matrix.csv"
GAPS = ROOT / "response-matrix" / "evidence-gap-list.csv"
SOURCES = ROOT / "source-docs" / "source-index.csv"
MARKED = ROOT / "response-matrix" / "marked-technical-clauses.csv"
OUTPUT = ROOT / "response-matrix" / "中央美术学院3.2交付内容与标准产品差距逐项核对表.xlsx"

NAVY = "17324D"
BLUE = "2F5D8C"
PALE_BLUE = "DDEBF7"
PALE_GREEN = "E2F0D9"
PALE_YELLOW = "FFF2CC"
PALE_ORANGE = "FCE4D6"
PALE_RED = "F4CCCC"
PALE_GRAY = "E7E6E6"
WHITE = "FFFFFF"
TEXT = "243447"
THIN = Side(style="thin", color="CBD5E1")


def read_csv(path: Path) -> list[dict[str, str]]:
    with path.open(encoding="utf-8-sig", newline="") as f:
        return list(csv.DictReader(f))


def tender_rows(docx_path: Path) -> list[dict[str, str]]:
    doc = Document(docx_path)
    table = next(
        table
        for table in doc.tables
        if table.rows and "技术参数要求" in " ".join(c.text for c in table.rows[0].cells)
    )
    if len(table.rows) != 17:
        raise ValueError(f"Expected 17 rows in section 3.2 table, got {len(table.rows)}")
    rows = []
    for row in table.rows[1:]:
        no, name, qty, requirement = [cell.text.strip() for cell in row.cells]
        rows.append({"source_item": no, "item": name, "quantity_text": qty, "requirement": requirement})
    return rows


def marker_type(line: str) -> str:
    if line.startswith("★"):
        return "★"
    if line.startswith("#"):
        return "#"
    if line.startswith("◎"):
        return "◎"
    return ""


def clause_domain(item_no: str, line: str) -> str:
    if item_no == "11":
        return "管理软件"
    if item_no == "9":
        return "硬件/AI算法"
    if item_no == "10":
        return "设备软件/留样流程"
    if item_no == "1" and any(k in line for k in ("补光灯", "内置安装客户端软件")):
        return "硬件/证明材料"
    if item_no == "2" and "内置安装客户端软件" in line:
        return "硬件/证明材料"
    return "设备软件/业务流程"


def extract_marked(requirements: list[dict[str, str]], matrix: list[dict[str, str]]) -> list[dict[str, str]]:
    matrix_by_item = {row["source_item"]: row for row in matrix}
    result = []
    for req in requirements:
        meta = matrix_by_item[req["source_item"]]
        for line in (s.strip() for s in req["requirement"].splitlines()):
            marker = marker_type(line)
            if not marker:
                continue
            proof = "是" if "提供" in line and ("公章" in line or "评测报告" in line or "软著" in line) else "否"
            result.append(
                {
                    "clause_id": f"KEY-{len(result)+1:03d}",
                    "source_item": req["source_item"],
                    "item": req["item"],
                    "marker": marker,
                    "delivery_domain": clause_domain(req["source_item"], line),
                    "clause_text": line,
                    "explicit_proof_required": proof,
                    "source_page": meta["source_page"],
                    "gap_class": meta["gap_class"],
                    "standard_mapping": meta["standard_mapping"],
                    "review_status": "未查看",
                    "review_note": "",
                }
            )
    counts = {m: sum(1 for row in result if row["marker"] == m) for m in ("#", "★", "◎")}
    if len(result) != 39 or counts != {"#": 28, "★": 10, "◎": 1}:
        raise ValueError(f"Marked clause mismatch: total={len(result)}, counts={counts}")
    with MARKED.open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(result[0]))
        writer.writeheader()
        writer.writerows(result)
    return result


def style_title(ws, title: str, subtitle: str, max_col: int) -> None:
    ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=max_col)
    ws.cell(1, 1, title)
    ws.cell(1, 1).font = Font(name="STSong", size=18, bold=True, color=WHITE)
    ws.cell(1, 1).fill = PatternFill("solid", fgColor=NAVY)
    ws.cell(1, 1).alignment = Alignment(vertical="center")
    ws.row_dimensions[1].height = 34
    ws.merge_cells(start_row=2, start_column=1, end_row=2, end_column=max_col)
    ws.cell(2, 1, subtitle)
    ws.cell(2, 1).font = Font(name="STSong", size=10, color="44546A")
    ws.cell(2, 1).fill = PatternFill("solid", fgColor=PALE_BLUE)
    ws.cell(2, 1).alignment = Alignment(wrap_text=True, vertical="center")
    ws.row_dimensions[2].height = 34


def style_header(ws, row: int, columns: int) -> None:
    for cell in ws[row][:columns]:
        cell.font = Font(name="STSong", size=10, bold=True, color=WHITE)
        cell.fill = PatternFill("solid", fgColor=BLUE)
        cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
        cell.border = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
    ws.row_dimensions[row].height = 32


def style_body(ws, start: int, end: int, columns: int) -> None:
    for row in ws.iter_rows(min_row=start, max_row=end, min_col=1, max_col=columns):
        for cell in row:
            cell.font = Font(name="STSong", size=10, color=TEXT)
            cell.alignment = Alignment(vertical="top", wrap_text=True)
            cell.border = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)


def add_review_validation(ws, cell_range: str) -> None:
    dv = DataValidation(type="list", formula1='"未查看,已确认,需调整,待补证,不适用"', allow_blank=False)
    ws.add_data_validation(dv)
    dv.add(cell_range)


def add_status_rules(ws, cell_range: str, first_cell: str) -> None:
    rules = [
        ("未查看", PALE_GRAY),
        ("已确认", PALE_GREEN),
        ("需调整", PALE_ORANGE),
        ("待补证", PALE_YELLOW),
    ]
    for status, color in rules:
        ws.conditional_formatting.add(
            cell_range,
            FormulaRule(formula=[f'{first_cell}="{status}"'], fill=PatternFill("solid", fgColor=color)),
        )


def build_workbook(requirements, matrix, marked, gaps, sources) -> None:
    req_by_item = {row["source_item"]: row for row in requirements}
    wb = Workbook()
    ws = wb.active
    ws.title = "00-结论总览"
    ws.sheet_view.showGridLines = False
    style_title(ws, "中央美术学院 3.2 交付内容与标准产品差距", "内部逐项核对版｜存在标准映射不等于已满足；报价、承诺、型号、证据、SLA、等保与工期均须人工签认", 8)
    ws["A4"] = "核心统计"
    ws["A4"].font = Font(name="STSong", size=13, bold=True, color=NAVY)
    labels = [
        (6, "3.2交付组数", "=COUNTA('01-逐项核对'!B5:B20)", "组"),
        (7, "A 标品配置", '=COUNTIF(\'01-逐项核对\'!H5:H20,"A-*")', "组"),
        (8, "B 适配验证", '=COUNTIF(\'01-逐项核对\'!H5:H20,"B-*")', "组"),
        (9, "C 替代采购/新SKU", '=COUNTIF(\'01-逐项核对\'!H5:H20,"C-*")', "组"),
        (10, "D 项目定制/服务", '=COUNTIF(\'01-逐项核对\'!H5:H20,"D-*")', "组"),
        (12, "重点标记条款", "=COUNTA('02-重点条款'!B5:B43)", "条"),
        (13, "# 重要指标", '=COUNTIF(\'02-重点条款\'!D5:D43,"#")', "条"),
        (14, "★ 实质性指标", '=COUNTIF(\'02-重点条款\'!D5:D43,"★")', "条"),
        (15, "◎ 标记项", '=COUNTIF(\'02-重点条款\'!D5:D43,"◎")', "条"),
    ]
    for row, label, formula, unit in labels:
        ws.cell(row, 1, label)
        ws.cell(row, 2, formula)
        ws.cell(row, 3, unit)
        ws.cell(row, 1).font = Font(name="STSong", size=10, bold=True, color=TEXT)
        ws.cell(row, 2).font = Font(name="STSong", size=16, bold=True, color=NAVY)
        ws.cell(row, 3).font = Font(name="STSong", size=10, color="64748B")
    ws["E4"] = "当前判断"
    ws["E4"].font = Font(name="STSong", size=13, bold=True, color=NAVY)
    conclusions = [
        "明确替代采购/项目定制：C+D = 5/16（31.3%）",
        "不能按开箱即用标品承诺：B+C+D = 14/16（87.5%）",
        "最明确参数偏差：保温餐炉、智能托盘",
        "最大项目风险：一卡通接口、3年SLA、等保与20日交钥匙",
        "DoR: BLOCKED｜产品/研发/硬件/供应链/交付/安全/法务/投标尚未签认",
    ]
    for idx, text in enumerate(conclusions, 6):
        ws.merge_cells(start_row=idx, start_column=5, end_row=idx, end_column=8)
        ws.cell(idx, 5, text)
        ws.cell(idx, 5).font = Font(name="STSong", size=10, bold=idx in (6, 7, 10), color="9C0006" if idx in (6, 7, 10) else TEXT)
        ws.cell(idx, 5).fill = PatternFill("solid", fgColor=PALE_RED if idx in (6, 7, 10) else PALE_YELLOW)
        ws.cell(idx, 5).alignment = Alignment(wrap_text=True, vertical="center")
        ws.row_dimensions[idx].height = 44
    ws["A18"] = "审核顺序"
    ws["A18"].font = Font(name="STSong", size=13, bold=True, color=NAVY)
    steps = [
        "1. 在“01-逐项核对”逐行确认16个交付组。",
        "2. 在“02-重点条款”先看10个★，再看28个#。",
        "3. 在“03-软硬件拆分”核对真正要交的客户端、平台、设备、接口和服务。",
        "4. 在“04-差距补证”指定负责人、证据和关闭状态。",
    ]
    for idx, text in enumerate(steps, 19):
        ws.merge_cells(start_row=idx, start_column=1, end_row=idx, end_column=8)
        ws.cell(idx, 1, text)
        ws.cell(idx, 1).font = Font(name="STSong", size=10, color=TEXT)
        ws.cell(idx, 1).alignment = Alignment(wrap_text=True)
    for col, width in {"A": 24, "B": 14, "C": 9, "D": 4, "E": 24, "F": 18, "G": 18, "H": 18}.items():
        ws.column_dimensions[col].width = width
    ws.freeze_panes = "A4"

    ws = wb.create_sheet("01-逐项核对")
    ws.sheet_view.showGridLines = False
    headers = ["审核状态", "序号", "交付内容", "数量", "单位", "交付域", "原文页码", "差距分类", "标准产品/能力映射", "当前证据", "差距/风险", "下一门禁", "3.2原文要求", "审核意见"]
    style_title(ws, "3.2 第1—16项逐项核对", "浅黄色“审核状态/审核意见”可直接填写；A/B/C/D为内部工作量分类，不是投标合规结论", len(headers))
    for col, value in enumerate(headers, 1):
        ws.cell(4, col, value)
    style_header(ws, 4, len(headers))
    for row_idx, meta in enumerate(matrix, 5):
        req = req_by_item[meta["source_item"]]
        values = [
            meta["review_status"], meta["source_item"], meta["item"], meta["quantity"], meta["unit"],
            meta["delivery_domain"], meta["source_page"], meta["gap_class"], meta["standard_mapping"],
            meta["current_evidence"], meta["gap_or_risk"], meta["next_gate"], req["requirement"], meta["review_note"],
        ]
        for col, value in enumerate(values, 1):
            ws.cell(row_idx, col, value)
        ws.cell(row_idx, 1).fill = PatternFill("solid", fgColor=PALE_YELLOW)
        ws.cell(row_idx, 14).fill = PatternFill("solid", fgColor=PALE_YELLOW)
        ws.row_dimensions[row_idx].height = 118
    style_body(ws, 5, 20, len(headers))
    add_review_validation(ws, "A5:A20")
    add_status_rules(ws, "A5:A20", "A5")
    gap_colors = {"A": PALE_GREEN, "B": PALE_YELLOW, "C": PALE_ORANGE, "D": PALE_RED}
    for row in range(5, 21):
        ws.cell(row, 8).fill = PatternFill("solid", fgColor=gap_colors[ws.cell(row, 8).value[0]])
        ws.cell(row, 8).font = Font(name="STSong", size=10, bold=True, color=TEXT)
    ws.auto_filter.ref = f"A4:N20"
    ws.freeze_panes = "A5"
    widths = [12, 7, 22, 8, 7, 19, 14, 18, 34, 46, 48, 42, 72, 28]
    for idx, width in enumerate(widths, 1):
        ws.column_dimensions[chr(64 + idx)].width = width
    ws.sheet_properties.pageSetUpPr.fitToPage = True
    ws.page_setup.orientation = "landscape"
    ws.page_setup.fitToWidth = 1
    ws.page_setup.fitToHeight = 0

    ws = wb.create_sheet("02-重点条款")
    ws.sheet_view.showGridLines = False
    headers = ["审核状态", "条款ID", "采购序号", "标记", "标的", "归属", "重点条款原文", "明确要求证明材料", "原文页码", "差距分类", "标准映射", "审核意见"]
    style_title(ws, "3.2 重点标记条款", "共39条：28个#、10个★、1个◎；★负偏离可能导致无效响应，优先审核", len(headers))
    for col, value in enumerate(headers, 1):
        ws.cell(4, col, value)
    style_header(ws, 4, len(headers))
    for row_idx, row in enumerate(marked, 5):
        values = [row["review_status"], row["clause_id"], row["source_item"], row["marker"], row["item"], row["delivery_domain"], row["clause_text"], row["explicit_proof_required"], row["source_page"], row["gap_class"], row["standard_mapping"], row["review_note"]]
        for col, value in enumerate(values, 1):
            ws.cell(row_idx, col, value)
        ws.cell(row_idx, 1).fill = PatternFill("solid", fgColor=PALE_YELLOW)
        ws.cell(row_idx, 12).fill = PatternFill("solid", fgColor=PALE_YELLOW)
        ws.cell(row_idx, 4).fill = PatternFill("solid", fgColor=PALE_RED if row["marker"] == "★" else PALE_YELLOW if row["marker"] == "#" else PALE_BLUE)
        ws.cell(row_idx, 4).font = Font(name="STSong", size=12, bold=True, color="9C0006" if row["marker"] == "★" else TEXT)
        ws.row_dimensions[row_idx].height = 62
    style_body(ws, 5, 43, len(headers))
    add_review_validation(ws, "A5:A43")
    add_status_rules(ws, "A5:A43", "A5")
    ws.auto_filter.ref = "A4:L43"
    ws.freeze_panes = "A5"
    widths = [12, 12, 9, 7, 21, 20, 74, 15, 14, 18, 35, 28]
    for idx, width in enumerate(widths, 1):
        ws.column_dimensions[chr(64 + idx)].width = width

    ws = wb.create_sheet("03-软硬件拆分")
    ws.sheet_view.showGridLines = False
    headers = ["拆分ID", "父项", "交付域", "具体交付物", "数量/范围", "标准映射", "差距分类", "验收重点"]
    style_title(ws, "软件、硬件与服务交付拆分", "把软硬一体设备拆成硬件、客户端、平台、接口和服务，避免漏项", len(headers))
    for col, value in enumerate(headers, 1):
        ws.cell(4, col, value)
    style_header(ws, 4, len(headers))
    split_rows = [
        ("HW-01", "1", "硬件", "计量称重一体机整机", "20台", "CPT-Nutr-GMSC410候选", "B-适配验证", "屏幕/秤/扫码/RJ45/补光/RGB声光/量程/封样"),
        ("SW-01", "1", "设备软件", "称重终端客户端", "20台授权/安装", "SKU-SW-03", "B-适配验证", "显示、解绑、上传、离线、余量阈值、同步、激活、报警"),
        ("HW-02", "2", "硬件", "餐盘身份绑定终端整机", "2台", "CPT-BP001", "A-标品配置", "人脸/IC卡/扫码/RJ45/Wi-Fi/蓝牙/封样"),
        ("SW-02", "2", "设备软件", "绑盘终端客户端", "2台授权/安装", "SKU-SW-03", "A-标品配置", "绑定组合、状态、余额阈值、人员/人脸同步、自检、无感支付"),
        ("HW-03", "3", "硬件", "保温餐炉", "20套", "C320WL不匹配", "C-替代采购/新SKU", "304材质、≥700W、35-85℃数显、尺寸、称重配套"),
        ("HW-04", "4", "硬件", "PP激光镭雕条码智能托盘", "600个", "CPT-FT248/246不匹配", "C-替代采购/新SKU", "食品级PP、尺寸、条码耐久、后续补货一致"),
        ("ENG-01", "5", "定制工程", "石材自助餐台", "1套", "SC-NUT-SVC-001/CUSTOM", "D-项目定制/服务", "复尺、深化图、开孔、水电、承重、收口、设备配套"),
        ("HW-05", "6", "硬件", "≥65寸4K数据大屏", "2台", "CPT-Nutr-GMSCR580候选", "B-适配验证", "响应时间、色域、内存、Wi-Fi、两屏型号"),
        ("SW-03", "6", "展示软件", "经营/菜品/营养可视化页面", "2块屏内容", "ADD-08", "B-适配验证", "组件、数据口径、权限、刷新频率、断网状态"),
        ("HW-06", "7", "硬件", "管理平台与数据库运行终端", "1套", "TS90X/服务器候选", "B-适配验证", "CPU、32GB、SAS≥2T、7200rpm、RAID、OS/数据库"),
        ("HW-07", "8", "硬件", "24口千兆可网管交换机", "1台", "S5735-L24T4X-A1", "A-标品配置", "VLAN、QoS、DHCP Snooping、SFP、云/本地管理"),
        ("HW-08", "9", "硬件/AI", "智能晨检仪", "1台", "Y-6/S-1/DW-1候选", "B-适配验证", "Android11、双摄、测温精度/距离、IP54、手部AI"),
        ("HW-09", "10", "硬件", "智能留样秤", "1台", "ST-LYY-156", "B-适配验证", "屏幕、网络、打印、接口、与留样柜组合"),
        ("SW-04", "10", "设备软件", "留样终端客户端与记录同步", "1套", "FSA-01", "B-适配验证", "记录字段、云同步、本地导入导出、预存期锁定"),
        ("SW-05", "11", "管理软件", "智慧餐厅管理系统PC端", "1套", "SC-NUT-SW-001/SKU-SW-03", "B-适配验证", "18组功能、报表导出、设备/余量、排餐、解绑扣款"),
        ("SW-06", "11", "移动软件", "自助称重食堂手机管理端", "1套", "SC-NUT-SW-002/ADD-08", "B-适配验证", "分时营收、商品、流水、设备、销量与权限"),
        ("SW-07", "12", "食安软件", "明厨亮灶AI系统平台", "1套", "SC-FS-SW-001/ADD-06", "B-适配验证", "视频调阅、算法告警、整改闭环、权限和证据"),
        ("HW-10", "12", "AI硬件/视频", "AI分析盒+摄像头更新+布线", "1套/按点位", "CPT-Box600-08+摄像头候选", "B-适配验证", "模型、8路、编码、HDMI、接口、兼容、点位与布线"),
        ("INT-01", "13", "接口", "学校一卡通对接", "1项", "ADD-03/INT-01", "B-适配验证", "账户、卡/码/脸/NFC、扣费、对账、异常、幂等"),
        ("SVC-01", "14", "运维服务", "3年质保与7×24 SLA", "3年+终身升级", "SC-NUT/FS-SVC-MAINT", "D-项目定制/服务", "30分/2小时/4小时、月巡检、备件、备用机、责任"),
        ("SEC-01", "15", "安全合规", "本地化、人脸数据、等保与移交", "全系统", "ADD-07", "B-适配验证", "数据流、权限、审计、备份、境内、人脸、等保边界"),
        ("SVC-02", "16", "实施交付", "运输安装、部署配置、联调培训、竣工资料", "交钥匙/20日", "SC-NUT/FS-SVC-IMPLEMENT", "D-项目定制/服务", "现场WBS、强弱电/布线、停业窗口、联合测试、验收资料"),
    ]
    for row_idx, row in enumerate(split_rows, 5):
        for col, value in enumerate(row, 1):
            ws.cell(row_idx, col, value)
        ws.row_dimensions[row_idx].height = 54
        ws.cell(row_idx, 7).fill = PatternFill("solid", fgColor=gap_colors[row[6][0]])
    style_body(ws, 5, 4 + len(split_rows), len(headers))
    ws.auto_filter.ref = f"A4:H{4+len(split_rows)}"
    ws.freeze_panes = "A5"
    widths = [12, 8, 18, 34, 18, 36, 18, 58]
    for idx, width in enumerate(widths, 1):
        ws.column_dimensions[chr(64 + idx)].width = width

    ws = wb.create_sheet("04-差距补证")
    ws.sheet_view.showGridLines = False
    headers = ["缺口ID", "关联项", "优先级", "缺口", "所需证据", "建议Owner", "状态"]
    style_title(ws, "差距与补证清单", "P0缺口关闭前 DoR 保持 BLOCKED；Owner仅为建议，需项目负责人实际分派", len(headers))
    for col, value in enumerate(headers, 1):
        ws.cell(4, col, value)
    style_header(ws, 4, len(headers))
    for row_idx, row in enumerate(gaps, 5):
        values = [row["gap_id"], row["related_requirements"], row["priority"], row["gap"], row["required_evidence"], row["owner"], row["status"]]
        for col, value in enumerate(values, 1):
            ws.cell(row_idx, col, value)
        ws.cell(row_idx, 7).fill = PatternFill("solid", fgColor=PALE_YELLOW)
        ws.row_dimensions[row_idx].height = 60
    style_body(ws, 5, 4 + len(gaps), len(headers))
    ws.auto_filter.ref = f"A4:G{4+len(gaps)}"
    ws.freeze_panes = "A5"
    widths = [12, 22, 10, 52, 52, 28, 14]
    for idx, width in enumerate(widths, 1):
        ws.column_dimensions[chr(64 + idx)].width = width

    ws = wb.create_sheet("05-来源口径")
    ws.sheet_view.showGridLines = False
    headers = ["来源ID", "来源名称", "类型", "版本/日期", "SHA256", "位置", "角色", "证据等级", "备注"]
    style_title(ws, "来源、证据等级与分类口径", "采购文件为A类项目事实；标品主数据和软件SKU为B类内部基线，不能自动替代厂家/运行/接口/验收证据", len(headers))
    for col, value in enumerate(headers, 1):
        ws.cell(4, col, value)
    style_header(ws, 4, len(headers))
    keys = ["source_id", "source_name", "source_type", "version_or_date", "sha256", "location", "role", "evidence_level", "notes"]
    for row_idx, row in enumerate(sources, 5):
        for col, key in enumerate(keys, 1):
            ws.cell(row_idx, col, row[key])
        ws.row_dimensions[row_idx].height = 52
    style_body(ws, 5, 4 + len(sources), len(headers))
    start = 7 + len(sources)
    ws.cell(start, 1, "差距分类定义")
    ws.cell(start, 1).font = Font(name="STSong", size=13, bold=True, color=NAVY)
    definitions = [
        ("A-标品配置", "存在相对明确标准型号/能力；仍须补本项目型号、版本和验收证据。"),
        ("B-适配验证", "有邻近标品/能力底座，但参数、页面、接口、AI模型、安全或运行证据不足。"),
        ("C-替代采购/新SKU", "当前标准型号存在明确差异，需要替代采购、重新选型或建立新SKU。"),
        ("D-项目定制/服务", "依赖现场、工程、SLA、专项资源或超出默认产品/服务边界。"),
    ]
    for idx, (name, desc) in enumerate(definitions, start + 1):
        ws.cell(idx, 1, name)
        ws.cell(idx, 2, desc)
        ws.merge_cells(start_row=idx, start_column=2, end_row=idx, end_column=9)
        ws.cell(idx, 1).fill = PatternFill("solid", fgColor=gap_colors[name[0]])
        ws.cell(idx, 1).font = Font(name="STSong", size=10, bold=True, color=TEXT)
        ws.cell(idx, 2).font = Font(name="STSong", size=10, color=TEXT)
        ws.cell(idx, 2).alignment = Alignment(wrap_text=True)
    widths = [15, 34, 20, 16, 34, 48, 28, 12, 46]
    for idx, width in enumerate(widths, 1):
        ws.column_dimensions[chr(64 + idx)].width = width
    ws.freeze_panes = "A5"

    for ws in wb.worksheets:
        ws.sheet_properties.pageSetUpPr.fitToPage = True
        ws.page_margins.left = 0.25
        ws.page_margins.right = 0.25
        ws.page_margins.top = 0.4
        ws.page_margins.bottom = 0.4
        ws.sheet_view.zoomScale = 85
    wb.calculation.fullCalcOnLoad = True
    wb.calculation.forceFullCalc = True
    wb.save(OUTPUT)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("docx", type=Path)
    args = parser.parse_args()
    matrix = read_csv(MATRIX)
    gaps = read_csv(GAPS)
    sources = read_csv(SOURCES)
    requirements = tender_rows(args.docx)
    if len(matrix) != 16 or len(requirements) != 16:
        raise ValueError(f"Expected 16 matrix and requirement rows, got {len(matrix)} and {len(requirements)}")
    marked = extract_marked(requirements, matrix)
    build_workbook(requirements, matrix, marked, gaps, sources)
    print(f"generated={OUTPUT}")
    print(f"matrix_rows={len(matrix)} marked_rows={len(marked)} gaps={len(gaps)}")


if __name__ == "__main__":
    main()
