#!/usr/bin/env python3
"""Build the branded Arabic Sawaed ERP 1.29 user guide DOCX from the canonical JSON."""

from __future__ import annotations

import json
from pathlib import Path

from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Inches, Pt, RGBColor


ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "resources/user-guide-1.29.json"
OUTPUT_DIR = ROOT / "resources/guides"
OUTPUT = OUTPUT_DIR / "sawaed-user-guide-1.29-ar.docx"
LOGO = ROOT / "public/assets/branding/sawaed-arab-company-logo.jpeg"

# compact_reference_guide preset + named regional branding overrides.
FONT = "DejaVu Sans"
ORANGE = "ED7D12"
ORANGE_DARK = "B95600"
INK = "1F2933"
MUTED = "667383"
LIGHT = "F3F5F7"
ORANGE_SOFT = "FFF1E4"
TEAL_SOFT = "E8F6F4"
RED_SOFT = "FFF0F1"
BORDER = "DDE3E8"
CONTENT_DXA = 9360
TABLE_INDENT_DXA = 120
CELL_START_DXA = 120


def set_cell_shading(cell, fill: str) -> None:
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = tc_pr.find(qn("w:shd"))
    if shd is None:
        shd = OxmlElement("w:shd")
        tc_pr.append(shd)
    shd.set(qn("w:fill"), fill)


def set_cell_margins(cell, top=80, start=120, bottom=80, end=120) -> None:
    tc = cell._tc
    tc_pr = tc.get_or_add_tcPr()
    tc_mar = tc_pr.first_child_found_in("w:tcMar")
    if tc_mar is None:
        tc_mar = OxmlElement("w:tcMar")
        tc_pr.append(tc_mar)
    for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
        node = tc_mar.find(qn(f"w:{margin}"))
        if node is None:
            node = OxmlElement(f"w:{margin}")
            tc_mar.append(node)
        node.set(qn("w:w"), str(value))
        node.set(qn("w:type"), "dxa")


def set_repeat_table_header(row) -> None:
    tr_pr = row._tr.get_or_add_trPr()
    repeat = OxmlElement("w:tblHeader")
    repeat.set(qn("w:val"), "true")
    tr_pr.append(repeat)


def set_row_cant_split(row) -> None:
    tr_pr = row._tr.get_or_add_trPr()
    if tr_pr.find(qn("w:cantSplit")) is None:
        tr_pr.append(OxmlElement("w:cantSplit"))


def set_table_geometry(table, widths: list[int]) -> None:
    table.autofit = False
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    tbl_pr = table._tbl.tblPr
    tbl_w = tbl_pr.find(qn("w:tblW"))
    if tbl_w is None:
        tbl_w = OxmlElement("w:tblW")
        tbl_pr.append(tbl_w)
    tbl_w.set(qn("w:w"), str(sum(widths)))
    tbl_w.set(qn("w:type"), "dxa")
    tbl_ind = tbl_pr.find(qn("w:tblInd"))
    if tbl_ind is None:
        tbl_ind = OxmlElement("w:tblInd")
        tbl_pr.append(tbl_ind)
    tbl_ind.set(qn("w:w"), str(TABLE_INDENT_DXA))
    tbl_ind.set(qn("w:type"), "dxa")
    grid = table._tbl.tblGrid
    for child in list(grid):
        grid.remove(child)
    for width in widths:
        grid_col = OxmlElement("w:gridCol")
        grid_col.set(qn("w:w"), str(width))
        grid.append(grid_col)
    for row in table.rows:
        set_row_cant_split(row)
        for idx, cell in enumerate(row.cells):
            tc_pr = cell._tc.get_or_add_tcPr()
            tc_w = tc_pr.find(qn("w:tcW"))
            if tc_w is None:
                tc_w = OxmlElement("w:tcW")
                tc_pr.append(tc_w)
            tc_w.set(qn("w:w"), str(widths[min(idx, len(widths) - 1)]))
            tc_w.set(qn("w:type"), "dxa")
            set_cell_margins(cell)
            cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER


def set_rtl(paragraph, *, align=WD_ALIGN_PARAGRAPH.RIGHT) -> None:
    paragraph.alignment = align
    p_pr = paragraph._p.get_or_add_pPr()
    bidi = p_pr.find(qn("w:bidi"))
    if bidi is None:
        bidi = OxmlElement("w:bidi")
        p_pr.append(bidi)
    bidi.set(qn("w:val"), "1")


def set_run(run, *, size=11, bold=False, color=INK, italic=False) -> None:
    run.font.name = FONT
    run._element.get_or_add_rPr().rFonts.set(qn("w:ascii"), FONT)
    run._element.get_or_add_rPr().rFonts.set(qn("w:hAnsi"), FONT)
    run._element.get_or_add_rPr().rFonts.set(qn("w:cs"), FONT)
    run.font.size = Pt(size)
    run.font.bold = bold
    run.font.italic = italic
    run.font.color.rgb = RGBColor.from_string(color)
    r_pr = run._element.get_or_add_rPr()
    rtl = r_pr.find(qn("w:rtl"))
    if rtl is None:
        rtl = OxmlElement("w:rtl")
        r_pr.append(rtl)
    rtl.set(qn("w:val"), "1")


def style_paragraph(paragraph, *, before=0, after=6, line=1.25, keep=False) -> None:
    set_rtl(paragraph)
    paragraph.paragraph_format.space_before = Pt(before)
    paragraph.paragraph_format.space_after = Pt(after)
    paragraph.paragraph_format.line_spacing = line
    paragraph.paragraph_format.keep_with_next = keep


def add_text(doc, text: str, *, size=11, bold=False, color=INK, before=0, after=6, align=WD_ALIGN_PARAGRAPH.RIGHT, italic=False):
    p = doc.add_paragraph()
    style_paragraph(p, before=before, after=after)
    set_rtl(p, align=align)
    set_run(p.add_run(text), size=size, bold=bold, color=color, italic=italic)
    return p


def add_heading(doc, text: str, level: int, bookmark: str | None = None):
    p = doc.add_paragraph(style=f"Heading {level}")
    style_paragraph(p, before={1:18,2:14,3:10}[level], after={1:10,2:7,3:5}[level], keep=True)
    run = p.add_run(text)
    set_run(run, size={1:16,2:13,3:12}[level], bold=True, color=ORANGE_DARK if level < 3 else INK)
    if bookmark:
        bookmark_start = OxmlElement("w:bookmarkStart")
        bookmark_start.set(qn("w:id"), str(abs(hash(bookmark)) % 2000000000))
        bookmark_start.set(qn("w:name"), bookmark)
        bookmark_end = OxmlElement("w:bookmarkEnd")
        bookmark_end.set(qn("w:id"), bookmark_start.get(qn("w:id")))
        p._p.insert(0, bookmark_start)
        p._p.append(bookmark_end)
    return p


def add_hyperlink(paragraph, text: str, anchor: str, *, color=ORANGE_DARK, bold=False) -> None:
    hyperlink = OxmlElement("w:hyperlink")
    hyperlink.set(qn("w:anchor"), anchor)
    run = OxmlElement("w:r")
    r_pr = OxmlElement("w:rPr")
    color_node = OxmlElement("w:color")
    color_node.set(qn("w:val"), color)
    r_pr.append(color_node)
    if bold:
        r_pr.append(OxmlElement("w:b"))
    r_fonts = OxmlElement("w:rFonts")
    for attr in ("ascii", "hAnsi", "cs"):
        r_fonts.set(qn(f"w:{attr}"), FONT)
    r_pr.append(r_fonts)
    rtl = OxmlElement("w:rtl")
    rtl.set(qn("w:val"), "1")
    r_pr.append(rtl)
    run.append(r_pr)
    text_node = OxmlElement("w:t")
    text_node.text = text
    run.append(text_node)
    hyperlink.append(run)
    paragraph._p.append(hyperlink)


def add_list(doc, items: list[str], *, numbered=False, checklist=False) -> None:
    style = "List Number" if numbered else "List Bullet"
    for item in items:
        p = doc.add_paragraph(style=style)
        style_paragraph(p, after=4)
        p.paragraph_format.right_indent = Inches(0.375)
        p.paragraph_format.first_line_indent = Inches(-0.188)
        set_run(p.add_run(item), size=10.5, color=INK)


def add_callout(doc, title: str, text: str, *, warning=False) -> None:
    table = doc.add_table(rows=1, cols=1)
    set_table_geometry(table, [CONTENT_DXA])
    table.style = "Table Grid"
    cell = table.cell(0, 0)
    set_cell_shading(cell, RED_SOFT if warning else TEAL_SOFT)
    p = cell.paragraphs[0]
    style_paragraph(p, after=3)
    set_run(p.add_run(title), size=10.5, bold=True, color="9B1C1C" if warning else "176B63")
    p2 = cell.add_paragraph()
    style_paragraph(p2, after=0)
    set_run(p2.add_run(text), size=10.2, color=INK)
    doc.add_paragraph().paragraph_format.space_after = Pt(2)


def add_table(doc, headers: list[str], rows: list[list[str]]) -> None:
    table = doc.add_table(rows=1, cols=len(headers))
    table.style = "Table Grid"
    widths = [CONTENT_DXA // len(headers)] * len(headers)
    widths[-1] += CONTENT_DXA - sum(widths)
    set_table_geometry(table, widths)
    set_repeat_table_header(table.rows[0])
    for idx, header in enumerate(headers):
        cell = table.rows[0].cells[idx]
        set_cell_shading(cell, ORANGE_SOFT)
        p = cell.paragraphs[0]
        style_paragraph(p, after=0)
        set_run(p.add_run(str(header)), size=9.5, bold=True, color=ORANGE_DARK)
    for row_data in rows:
        cells = table.add_row().cells
        for idx, value in enumerate(row_data):
            p = cells[idx].paragraphs[0]
            style_paragraph(p, after=0, line=1.15)
            set_run(p.add_run(str(value)), size=9.2, color=INK)
    set_table_geometry(table, widths)
    doc.add_paragraph().paragraph_format.space_after = Pt(2)


def add_page_number(paragraph) -> None:
    set_rtl(paragraph, align=WD_ALIGN_PARAGRAPH.CENTER)
    set_run(paragraph.add_run("صفحة "), size=8.5, color=MUTED)
    run = paragraph.add_run()
    fld_char1 = OxmlElement("w:fldChar")
    fld_char1.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText")
    instr.set(qn("xml:space"), "preserve")
    instr.text = " PAGE "
    fld_char2 = OxmlElement("w:fldChar")
    fld_char2.set(qn("w:fldCharType"), "end")
    run._r.extend([fld_char1, instr, fld_char2])
    set_run(run, size=8.5, color=MUTED)


def configure_document(doc: Document) -> None:
    section = doc.sections[0]
    section.page_width = Inches(8.5)
    section.page_height = Inches(11)
    section.top_margin = Inches(0.78)
    section.bottom_margin = Inches(0.78)
    section.left_margin = Inches(1.0)
    section.right_margin = Inches(1.0)
    section.header_distance = Inches(0.35)
    section.footer_distance = Inches(0.35)
    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = FONT
    normal._element.rPr.rFonts.set(qn("w:ascii"), FONT)
    normal._element.rPr.rFonts.set(qn("w:hAnsi"), FONT)
    normal._element.rPr.rFonts.set(qn("w:cs"), FONT)
    normal.font.size = Pt(11)
    normal.font.color.rgb = RGBColor.from_string(INK)
    normal.paragraph_format.space_after = Pt(6)
    normal.paragraph_format.line_spacing = 1.25
    for level, size in ((1,16),(2,13),(3,12)):
        style = styles[f"Heading {level}"]
        style.font.name = FONT
        style._element.rPr.rFonts.set(qn("w:ascii"), FONT)
        style._element.rPr.rFonts.set(qn("w:hAnsi"), FONT)
        style._element.rPr.rFonts.set(qn("w:cs"), FONT)
        style.font.size = Pt(size)
        style.font.color.rgb = RGBColor.from_string(ORANGE_DARK if level < 3 else INK)
    # Running header and footer.
    hp = section.header.paragraphs[0]
    set_rtl(hp)
    hp.paragraph_format.space_after = Pt(0)
    set_run(hp.add_run("سواعد المتكامل | دليل المستخدم 1.29"), size=8.5, bold=True, color=MUTED)
    add_page_number(section.footer.paragraphs[0])


def cover(doc: Document, meta: dict) -> None:
    for _ in range(3):
        add_text(doc, "", after=8)
    p = doc.add_paragraph()
    set_rtl(p, align=WD_ALIGN_PARAGRAPH.CENTER)
    p.add_run().add_picture(str(LOGO), width=Inches(5.5))
    add_text(doc, "المرجع التشغيلي الرسمي", size=11, bold=True, color=ORANGE_DARK, before=34, after=12, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(doc, meta["title"], size=27, bold=True, color=INK, after=9, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(doc, "شرح شامل لجميع أقسام النظام ومسارات العمل والأدوار والصلاحيات", size=14, color=MUTED, after=28, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(doc, f"الإصدار {meta['version']}  |  {meta['updated']}", size=12, bold=True, color=ORANGE_DARK, after=6, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_text(doc, meta["company"], size=11, color=MUTED, after=40, align=WD_ALIGN_PARAGRAPH.CENTER)
    add_callout(doc, "نطاق الدليل", "يغطي هذا الدليل وظائف النسخة 1.29 الفعلية. ظهور الأقسام والأزرار يختلف حسب دور المستخدم وصلاحياته ونطاق الشركة أو المشروع.")
    doc.add_page_break()


def intro_and_toc(doc: Document, data: dict) -> None:
    add_heading(doc, "عن هذا الدليل", 1, bookmark="guide_intro")
    add_text(doc, "أُعد هذا الدليل ليكون مرجعًا يوميًا للمستخدم، ومادة تدريب للموظفين الجدد، وقائمة تحقق للمديرين ومسؤولي النظام. ابدأ بفصول تسجيل الدخول والتنقل، ثم انتقل إلى الفصل المرتبط بعملك.")
    add_heading(doc, "الأدوار الرئيسية", 2)
    add_table(doc, ["الدور", "نطاق الاستخدام الأساسي"], [[r["name"], r["scope"]] for r in data["roles"]])
    add_callout(doc, "مبدأ الصلاحيات", "عدم ظهور قسم أو زر غالبًا يعني أن الحساب لا يملك صلاحية العرض أو الإضافة أو التعديل. راجع مسؤول الوصول بدل استخدام حساب موظف آخر.")
    add_heading(doc, "فهرس الفصول", 1, bookmark="guide_toc")
    for index, chapter in enumerate(data["chapters"], 1):
        p = doc.add_paragraph()
        style_paragraph(p, after=3)
        add_hyperlink(p, f"{index}. {chapter['title']}", f"chapter_{index}", bold=True)
        set_run(p.add_run("  —  "+chapter["summary"]), size=9.3, color=MUTED)
    doc.add_page_break()


def chapter(doc: Document, chapter_data: dict, index: int, total: int) -> None:
    add_text(doc, f"الفصل {index} من {total}  |  {chapter_data['group']}", size=9.5, bold=True, color=ORANGE_DARK, after=5)
    add_heading(doc, chapter_data["title"], 1, bookmark=f"chapter_{index}")
    add_text(doc, chapter_data["summary"], size=11.5, color=MUTED, after=5)
    add_text(doc, "الفئة المستهدفة: "+chapter_data["audience"], size=9.2, bold=True, color=MUTED, after=14)
    for section in chapter_data["sections"]:
        section_type = section["type"]
        if section_type in ("note", "warning"):
            add_callout(doc, section["title"], section["text"], warning=section_type == "warning")
            continue
        add_heading(doc, section["title"], 2)
        if section_type == "text":
            add_text(doc, section["text"])
        elif section_type == "table":
            add_table(doc, section["headers"], section["rows"])
        else:
            add_list(doc, section["items"], numbered=section_type == "steps", checklist=section_type == "checklist")


def build() -> Path:
    data = json.loads(SOURCE.read_text(encoding="utf-8"))
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    doc = Document()
    configure_document(doc)
    cover(doc, data["meta"])
    intro_and_toc(doc, data)
    total = len(data["chapters"])
    for index, chapter_data in enumerate(data["chapters"], 1):
        chapter(doc, chapter_data, index, total)
    doc.core_properties.title = data["meta"]["title"]
    doc.core_properties.subject = "دليل التشغيل الشامل لنظام سواعد المتكامل إصدار 1.29"
    doc.core_properties.author = data["meta"]["company"]
    doc.core_properties.keywords = "سواعد، ERP، دليل مستخدم، مقاولات، مشاريع"
    doc.core_properties.comments = "تم توليد الدليل من المصدر التشغيلي الموحد resources/user-guide-1.29.json"
    doc.save(OUTPUT)
    return OUTPUT


if __name__ == "__main__":
    print(build())
