from docx import Document
from docx.enum.section import WD_SECTION
from docx.enum.table import WD_TABLE_ALIGNMENT, WD_CELL_VERTICAL_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Cm, Inches, Pt, RGBColor
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
import textwrap
import os


BASE_DIR = Path(r"C:\xampp\htdocs\sistem_fail")
OUT_DIR = BASE_DIR / "docs"
IMG_DIR = OUT_DIR / "urs_flowcharts"
DOCX_PATH = OUT_DIR / "URS_Sistem_Pengurusan_Fail_MAINPP.docx"

OUT_DIR.mkdir(exist_ok=True)
IMG_DIR.mkdir(exist_ok=True)


def get_font(size=28, bold=False):
    candidates = [
        r"C:\Windows\Fonts\arialbd.ttf" if bold else r"C:\Windows\Fonts\arial.ttf",
        r"C:\Windows\Fonts\calibrib.ttf" if bold else r"C:\Windows\Fonts\calibri.ttf",
    ]
    for path in candidates:
        if os.path.exists(path):
            return ImageFont.truetype(path, size=size)
    return ImageFont.load_default()


def draw_wrapped_text(draw, box, text, font, fill=(30, 41, 59), align="center", line_spacing=6):
    x1, y1, x2, y2 = box
    max_width = x2 - x1 - 28
    words = text.split()
    lines = []
    current = ""
    for word in words:
        test = (current + " " + word).strip()
        if draw.textbbox((0, 0), test, font=font)[2] <= max_width:
            current = test
        else:
            if current:
                lines.append(current)
            current = word
    if current:
        lines.append(current)
    line_heights = [draw.textbbox((0, 0), line, font=font)[3] for line in lines]
    total_h = sum(line_heights) + line_spacing * max(0, len(lines) - 1)
    y = y1 + ((y2 - y1) - total_h) / 2
    for line, h in zip(lines, line_heights):
        w = draw.textbbox((0, 0), line, font=font)[2]
        if align == "center":
            x = x1 + ((x2 - x1) - w) / 2
        else:
            x = x1 + 16
        draw.text((x, y), line, font=font, fill=fill)
        y += h + line_spacing


def rounded_rect(draw, box, fill, outline=(148, 163, 184), width=2, radius=20):
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)


def arrow(draw, start, end, fill=(30, 64, 175), width=4):
    draw.line([start, end], fill=fill, width=width)
    ex, ey = end
    sx, sy = start
    if abs(ex - sx) > abs(ey - sy):
        direction = 1 if ex > sx else -1
        pts = [(ex, ey), (ex - 14 * direction, ey - 8), (ex - 14 * direction, ey + 8)]
    else:
        direction = 1 if ey > sy else -1
        pts = [(ex, ey), (ex - 8, ey - 14 * direction), (ex + 8, ey - 14 * direction)]
    draw.polygon(pts, fill=fill)


def make_flowchart(filename, title, nodes, edges, size=(1800, 1100)):
    img = Image.new("RGB", size, "white")
    draw = ImageDraw.Draw(img)
    title_font = get_font(42, bold=True)
    node_font = get_font(28)
    note_font = get_font(22)
    draw.text((70, 42), title, font=title_font, fill=(15, 23, 42))
    draw.line((70, 105, size[0] - 70, 105), fill=(203, 213, 225), width=3)

    for a, b in edges:
        ax1, ay1, ax2, ay2, _ = nodes[a]
        bx1, by1, bx2, by2, _ = nodes[b]
        start = ((ax1 + ax2) // 2, ay2)
        end = ((bx1 + bx2) // 2, by1)
        if abs(((ax1 + ax2) // 2) - ((bx1 + bx2) // 2)) > 20:
            start = (ax2, (ay1 + ay2) // 2) if bx1 > ax2 else (ax1, (ay1 + ay2) // 2)
            end = (bx1, (by1 + by2) // 2) if bx1 > ax2 else (bx2, (by1 + by2) // 2)
        arrow(draw, start, end)

    palette = [
        (239, 246, 255),
        (236, 253, 245),
        (255, 247, 237),
        (248, 250, 252),
        (254, 242, 242),
        (245, 243, 255),
    ]
    for idx, (key, value) in enumerate(nodes.items()):
        x1, y1, x2, y2, text = value
        rounded_rect(draw, (x1, y1, x2, y2), palette[idx % len(palette)])
        draw_wrapped_text(draw, (x1, y1, x2, y2), text, node_font)

    draw.text((70, size[1] - 56), "Dokumen URS dijana berdasarkan semakan kod PHP dan skema database sistem_fail_mainpp.", font=note_font, fill=(100, 116, 139))
    path = IMG_DIR / filename
    img.save(path)
    return path


def cell_shading(cell, fill):
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:fill"), fill)
    tc_pr.append(shd)


def set_cell_text(cell, text, bold=False, color="111827"):
    cell.text = ""
    p = cell.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    run = p.add_run(str(text))
    run.font.name = "Arial"
    run.font.size = Pt(9)
    run.bold = bold
    run.font.color.rgb = RGBColor.from_string(color)
    cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.TOP


def add_table(doc, headers, rows, widths=None):
    table = doc.add_table(rows=1, cols=len(headers))
    table.style = "Table Grid"
    table.alignment = WD_TABLE_ALIGNMENT.LEFT
    for idx, header in enumerate(headers):
        set_cell_text(table.rows[0].cells[idx], header, bold=True, color="0F172A")
        cell_shading(table.rows[0].cells[idx], "EAF3F8")
    for row in rows:
        cells = table.add_row().cells
        for idx, value in enumerate(row):
            set_cell_text(cells[idx], value)
    if widths:
        for row in table.rows:
            for idx, width in enumerate(widths):
                row.cells[idx].width = Cm(width)
    doc.add_paragraph()
    return table


def remove_trailing_empty_paragraphs(doc):
    while doc.paragraphs and not doc.paragraphs[-1].text.strip():
        paragraph = doc.paragraphs[-1]._element
        paragraph.getparent().remove(paragraph)


def add_bullet(doc, text):
    p = doc.add_paragraph(style="List Bullet")
    p.paragraph_format.space_after = Pt(4)
    p.add_run(text)


def add_number(doc, text):
    p = doc.add_paragraph(style="List Number")
    p.paragraph_format.space_after = Pt(4)
    p.add_run(text)


def set_styles(doc):
    styles = doc.styles
    normal = styles["Normal"]
    normal.font.name = "Arial"
    normal.font.size = Pt(10.5)
    normal.paragraph_format.space_after = Pt(6)
    for name, size, color in [
        ("Title", 22, "0F172A"),
        ("Heading 1", 16, "0F4C81"),
        ("Heading 2", 13, "0F172A"),
        ("Heading 3", 11, "334155"),
    ]:
        style = styles[name]
        style.font.name = "Arial"
        style.font.size = Pt(size)
        style.font.bold = True
        style.font.color.rgb = RGBColor.from_string(color)


def add_header_footer(section):
    header = section.header
    hp = header.paragraphs[0]
    hp.text = "URS Sistem Pengurusan Fail MAINPP"
    hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    hp.runs[0].font.size = Pt(8)
    hp.runs[0].font.color.rgb = RGBColor(100, 116, 139)
    footer = section.footer
    fp = footer.paragraphs[0]
    fp.text = "Dokumen URS | Semakan kod dan database sistem_fail"
    fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    fp.runs[0].font.size = Pt(8)
    fp.runs[0].font.color.rgb = RGBColor(100, 116, 139)


def add_section_intro(doc, title, body):
    doc.add_heading(title, level=1)
    for para in body:
        doc.add_paragraph(para)


def main():
    flow_auth = make_flowchart(
        "flow_01_auth.png",
        "Flow 1: Pendaftaran, Kelulusan dan Log Masuk",
        {
            "a": (90, 170, 430, 290, "Pengguna daftar akaun dengan emel rasmi MAINPP"),
            "b": (550, 170, 890, 290, "Sistem semak IC, emel unik dan domain @mainpp.gov.my"),
            "c": (1010, 170, 1350, 290, "Akaun disimpan sebagai pending"),
            "d": (1010, 410, 1350, 530, "Admin/Superadmin lulus atau tolak pengguna"),
            "e": (550, 410, 890, 530, "Pengguna log masuk; sistem semak status dan password"),
            "f": (90, 410, 430, 530, "Dashboard dipaparkan ikut role, bahagian dan permission"),
        },
        [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f")],
    )
    flow_folder = make_flowchart(
        "flow_02_folder_document.png",
        "Flow 2: Folder, Jilid dan Dokumen",
        {
            "a": (90, 170, 420, 290, "Pengguna pilih Tambah Folder"),
            "b": (520, 170, 850, 290, "Kod asas disahkan: MAINPP/BTM/100-19/1/1"),
            "c": (950, 170, 1280, 290, "Sistem jana JLD.1 atau JLD seterusnya"),
            "d": (1380, 170, 1710, 290, "Folder aktif dalam Senarai Folder"),
            "e": (950, 430, 1280, 550, "Tambah Fail/Surat dalam folder aktif"),
            "f": (520, 430, 850, 550, "No rujukan dipapar: folder_code(file_numbering)"),
            "g": (90, 430, 420, 550, "Jika folder ditutup, dokumen baharu disekat dan JLD baharu boleh dijana"),
        },
        [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("f", "g")],
    )
    flow_draft = make_flowchart(
        "flow_03_draft_support.png",
        "Flow 3: Cipta Draf Surat dan Sokongan",
        {
            "a": (90, 160, 420, 280, "Pilih folder office aktif"),
            "b": (520, 160, 850, 280, "Isi penerima, perkara, tarikh, jenis dan kandungan surat"),
            "c": (950, 160, 1280, 280, "Tambah atau delete page surat jika perlu"),
            "d": (1380, 160, 1710, 280, "Pilih pegawai sokongan jika surat perlukan tandatangan sokongan"),
            "e": (1380, 420, 1710, 540, "Emel dihantar kepada pegawai berdaftar"),
            "f": (950, 420, 1280, 540, "Pegawai log masuk dan beri keputusan disokong/ditolak"),
            "g": (520, 420, 850, 540, "Dashboard dan Senarai Draf tunjuk status sokongan"),
            "h": (90, 420, 420, 540, "Paparan/print A4 dengan penutup dan tandatangan"),
        },
        [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("f", "g"), ("g", "h")],
    )
    flow_admin = make_flowchart(
        "flow_04_admin_permission.png",
        "Flow 4: Capaian, Audit dan Data Dipadam",
        {
            "a": (120, 170, 460, 290, "Superadmin buka Capaian Pengguna"),
            "b": (570, 170, 910, 290, "Tetapkan permission ikut bahagian"),
            "c": (1020, 170, 1360, 290, "Tetapkan override capaian individu"),
            "d": (120, 430, 460, 550, "Aktiviti pengguna direkod dalam activity_logs"),
            "e": (570, 430, 910, 550, "Folder/Dokumen/Draf dipadam secara soft delete"),
            "f": (1020, 430, 1360, 550, "Data Dipadam boleh dilihat/pulihkan ikut capaian"),
        },
        [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f")],
        size=(1500, 950),
    )

    doc = Document()
    set_styles(doc)
    section = doc.sections[0]
    section.page_width = Cm(21)
    section.page_height = Cm(29.7)
    section.top_margin = Cm(1.8)
    section.bottom_margin = Cm(1.8)
    section.left_margin = Cm(1.8)
    section.right_margin = Cm(1.8)
    add_header_footer(section)

    title = doc.add_paragraph()
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = title.add_run("USER REQUIREMENT SPECIFICATION (URS)")
    run.bold = True
    run.font.size = Pt(22)
    run.font.color.rgb = RGBColor(15, 76, 129)
    subtitle = doc.add_paragraph()
    subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER
    r = subtitle.add_run("Sistem Pengurusan Fail MAINPP")
    r.bold = True
    r.font.size = Pt(15)
    doc.add_paragraph()
    meta_rows = [
        ("Nama Sistem", "Sistem Pengurusan Fail"),
        ("Folder Kod", r"C:\xampp\htdocs\sistem_fail"),
        ("Database", "sistem_fail_mainpp"),
        ("Tarikh Semakan", "21 Mei 2026"),
        ("Skop Semakan", "Kod PHP, konfigurasi emel, skema MySQL, permission, dan aliran pengguna end-to-end"),
    ]
    add_table(doc, ["Medan", "Maklumat"], meta_rows, widths=[4.2, 12.8])
    doc.add_paragraph("Dokumen ini disediakan berdasarkan semakan terus terhadap fail kod sistem dan database semasa. Ia menerangkan keperluan pengguna, modul, peraturan proses, kawalan capaian, struktur data, serta flowchart operasi end-to-end.")
    doc.add_page_break()

    add_section_intro(doc, "1. Ringkasan Eksekutif", [
        "Sistem Pengurusan Fail MAINPP ialah aplikasi web PHP/MySQL untuk mengurus folder jabatan, dokumen/fail, draf surat, sokongan tandatangan, capaian pengguna, audit aktiviti dan data dipadam.",
        "Sistem menggunakan konsep role dan permission berlapis: superadmin mempunyai semua capaian, manakala admin/user dikawal oleh role permission, permission bahagian, dan override individu.",
        "Kod folder office dijana mengikut jilid. Contoh format semasa ialah MAINPP/BTM/100-19/1/1 JLD.1. Nombor rujukan surat/dokumen dipaparkan bersama file numbering seperti MAINPP/BTM/100-19/1/1 JLD.1(1).",
    ])
    add_bullet(doc, "Frontend utama menggunakan fail PHP standalone dengan sidebar seragam melalui renderUnifiedSidebarMenu().")
    add_bullet(doc, "Sistem menyokong soft delete untuk folder, dokumen dan draf surat, dengan pemulihan melalui modul Data Dipadam.")
    add_bullet(doc, "Draf surat menyokong multi-page A4, tambah/delete page, paparan cetak, dan permintaan sokongan melalui emel kepada pengguna berdaftar.")
    add_bullet(doc, "Sesi pengguna tamat automatik selepas 10 minit tidak aktif dengan amaran 60 saat.")

    doc.add_heading("2. Skop Sistem", level=1)
    add_table(doc, ["Dalam Skop", "Penerangan"], [
        ("Pendaftaran dan login", "Pengguna mendaftar, menunggu kelulusan, log masuk, reset kata laluan melalui token emel."),
        ("Pengurusan folder", "Tambah, lihat, kemaskini, tutup, padam dan pulihkan folder. Folder office menjana JLD secara automatik."),
        ("Pengurusan dokumen/fail", "Tambah dokumen dalam folder aktif, muat naik lampiran, kemaskini metadata, muat turun, padam dan pulihkan."),
        ("Draf surat", "Cipta/kemaskini draf surat, kandungan multi-page, status draf/siap, sokongan emel, paparan dan cetak A4."),
        ("Capaian pengguna", "Permission ikut role, bahagian dan individu untuk folder, dokumen, draf surat dan data dipadam."),
        ("Audit dan pemantauan", "Activity log untuk login/logout, tindakan folder, dokumen, draf dan auto logout."),
    ], widths=[4.8, 12.2])

    doc.add_heading("3. Aktor dan Peranan", level=1)
    add_table(doc, ["Aktor", "Peranan Utama", "Capaian Umum"], [
        ("Superadmin", "Pentadbir penuh sistem", "Semua modul, konfigurasi capaian, user management, audit, data dipadam."),
        ("Admin", "Pentadbir operasi bahagian mengikut permission", "Folder/dokumen/draf mengikut bahagian dan permission yang dibenarkan."),
        ("User", "Pengguna operasi harian", "Lihat dan cipta folder/dokumen/draf mengikut permission bahagian atau override individu."),
        ("Pegawai Sokongan", "Pengguna berdaftar yang dipilih untuk memberi sokongan surat", "Menerima emel, log masuk, semak draf dan pilih disokong/ditolak."),
        ("Tetamu/Pemohon", "Individu belum log masuk", "Daftar akaun, login, forgot password dan reset password."),
    ], widths=[3.5, 6.2, 7.3])

    doc.add_heading("4. Modul dan Fail Kod Utama", level=1)
    add_table(doc, ["Modul", "Fail Utama", "Fungsi"], [
        ("Autentikasi", "login.php, process_login.php, auth.php, logout.php", "Login, sesi, auto logout, role gate, remember email/password cookie."),
        ("Pendaftaran pengguna", "register.php, process_register.php, user_approval.php, user_list.php", "Daftar akaun, kelulusan, tukar status dan role."),
        ("Folder", "add_folder.php, list_folder.php, view_folder.php, process_folder.php", "Tambah, senarai, lihat, close, delete, restore folder."),
        ("Dokumen", "add_documents.php, edit_document.php, process_document.php, download.php", "Tambah/kemaskini dokumen, lampiran, muat turun, delete/restore."),
        ("Draf surat", "draft_letter_form.php, draft_letters.php, draft_letter_print.php, process_draft_letter.php", "Draf multi-page, sokongan emel, paparan/print A4."),
        ("Dashboard dan carian", "dashboard.php, search.php", "Ringkasan folder/dokumen, status terkini, carian lanjutan."),
        ("Konfigurasi capaian", "system_config.php, db.php", "Permission matrix bahagian dan individu."),
        ("Audit dan recycle bin", "user_activity.php, deleted_items.php", "Pemantauan aktiviti dan pemulihan data soft delete."),
    ], widths=[3.4, 6.2, 7.4])

    doc.add_heading("5. Struktur Database Semasa", level=1)
    doc.add_paragraph("Database semasa: sistem_fail_mainpp. Ringkasan table dan tujuan adalah seperti berikut.")
    add_table(doc, ["Table", "Anggaran Rekod", "Tujuan"], [
        ("users", "7", "Akaun pengguna, role, status, bahagian, profile photo dan last_login."),
        ("folders", "1", "Folder rasmi/sendirian, kod JLD, bahagian, soft delete dan close status."),
        ("documents", "0", "Metadata dokumen, ref_no, file_numbering, lampiran utama dan soft delete."),
        ("document_attachments", "0", "Fail lampiran berbilang untuk dokumen."),
        ("letter_drafts", "0", "Draf surat, kandungan, halaman, tandatangan, status sokongan."),
        ("role_permissions", "46", "Permission asas mengikut role."),
        ("department_permissions", "228", "Permission mengikut bahagian/unit."),
        ("user_permissions", "0", "Override permission individu."),
        ("activity_logs", "63", "Audit login/logout dan tindakan sistem."),
        ("password_resets", "0", "Token reset kata laluan dan tarikh luput."),
        ("custom_fields", "0", "Medan custom untuk folder/dokumen."),
        ("folder_delete_log", "0", "Log padam folder."),
        ("document_delete_log", "1", "Log padam dokumen."),
    ], widths=[4.3, 2.5, 10.2])

    doc.add_heading("6. Entiti Data Kritikal", level=1)
    add_table(doc, ["Entiti", "Field Kritikal", "Nota Perniagaan"], [
        ("folders", "folder_code, folder_name, department, folder_type, closed_at, deleted_at", "Kod folder office mesti format rasmi dan akan ditambah JLD.n secara automatik."),
        ("documents", "folder_id, ref_no, file_numbering, subject, doc_date, from_person, to_person, doc_type", "Paparan no rujukan menggabungkan ref_no dengan file_numbering dalam kurungan."),
        ("letter_drafts", "document_id, folder_id, ref_no, letter_pages, support_status, support_user_id", "Setiap draf dikaitkan kepada satu dokumen melalui unique document_id."),
        ("permissions", "module, action, is_allowed", "Permission disemak melalui user override, kemudian department, kemudian role default."),
        ("activity_logs", "user_id, activity_type, activity_description, created_at", "Menyimpan jejak aktiviti untuk audit pentadbiran."),
    ], widths=[3.3, 7.0, 6.7])

    doc.add_heading("7. Keperluan Fungsian End-to-End", level=1)
    requirements = [
        ("URS-01", "Pendaftaran pengguna", "Sistem mesti membenarkan pengguna mendaftar menggunakan emel rasmi @mainpp.gov.my, IC unik dan emel unik.", "process_register.php"),
        ("URS-02", "Kelulusan pengguna", "Akaun baharu mesti berada dalam status pending sehingga diluluskan atau ditolak oleh admin/superadmin.", "user_approval.php, user_list.php"),
        ("URS-03", "Login dan sesi", "Sistem mesti menghalang login akaun pending/rejected dan menamatkan sesi tidak aktif selepas 10 minit.", "process_login.php, auth.php"),
        ("URS-04", "Cipta folder office", "Sistem mesti menjana kod JLD secara automatik berdasarkan kod asas folder dan menyekat JLD baharu jika folder asas masih aktif.", "process_folder.php, db.php"),
        ("URS-05", "Tutup folder", "Pengguna yang diberi capaian close folder boleh menutup folder; selepas ditutup, dokumen/draf baharu tidak boleh ditambah.", "list_folder.php, process_folder.php"),
        ("URS-06", "Tambah dokumen", "Sistem mesti membenarkan dokumen ditambah hanya dalam folder aktif dan menjana file_numbering seterusnya.", "add_documents.php, process_document.php"),
        ("URS-07", "Paparan no rujukan", "Sistem mesti memaparkan rujukan sebagai folder_code(file_numbering), contohnya MAINPP/BTM/100-19/1/1 JLD.1(1).", "db.php, dashboard.php, view_folder.php"),
        ("URS-08", "Draf surat multi-page", "Pengguna mesti boleh tambah dan delete page draf surat; footer dan tandatangan diletakkan pada halaman akhir.", "draft_letter_form.php, draft_letter_print.php"),
        ("URS-09", "Sokongan surat melalui emel", "Jika surat perlukan sokongan, sistem mesti hantar emel kepada pengguna berdaftar dan memaparkan status sokongan di dashboard/senarai.", "process_draft_letter.php, db.php"),
        ("URS-10", "Carian dokumen", "Pengguna mesti boleh membuat carian lanjutan berdasarkan no rujukan, folder, perkara, tarikh, daripada/kepada dan jenis dokumen.", "search.php"),
        ("URS-11", "Data dipadam", "Folder/dokumen/draf dipadam secara soft delete dan boleh dipulihkan jika pengguna mempunyai capaian.", "deleted_items.php"),
        ("URS-12", "Audit aktiviti", "Sistem mesti merekod login, logout, auto logout dan tindakan penting pengguna.", "db.php, user_activity.php"),
    ]
    add_table(doc, ["ID", "Keperluan", "Butiran", "Fail Rujukan"], requirements, widths=[2.0, 3.8, 8.0, 3.2])

    doc.add_heading("8. Peraturan Perniagaan Utama", level=1)
    for item in [
        "Folder office menerima kod asas seperti MAINPP/BTM/100-19/1/1 dan sistem menjana JLD.1, JLD.2 dan seterusnya.",
        "Folder aktif bagi kod asas yang sama mesti ditutup sebelum JLD baharu boleh dijana.",
        "Folder yang closed_at tidak kosong tidak boleh menerima dokumen atau draf surat baharu.",
        "Dokumen dalam folder office menggunakan ref_no sama dengan folder_code dan file_numbering sebagai nombor surat berurutan.",
        "Jenis dokumen semasa: Memo, Surat Rasmi, Surat Laporan, Minit Bebas, Surat Jemputan dan Invois.",
        "Jenis dokumen Memo Kelulusan telah dibuang daripada enum dokumen.",
        "Draf surat perlu mempunyai sekurang-kurangnya folder, perkara, tarikh, jenis dokumen, organisasi penerima dan isi surat.",
        "Permintaan sokongan hanya dihantar kepada pengguna berdaftar berstatus approved.",
        "Superadmin boleh mengakses semua bahagian; admin/user terikat kepada bahagian sendiri kecuali permission tertentu membenarkan tindakan.",
    ]:
        add_bullet(doc, item)

    doc.add_heading("9. Permission Matrix Ringkas", level=1)
    add_table(doc, ["Role/Layer", "Folder", "Dokumen", "Draf Surat", "Data Dipadam"], [
        ("Superadmin", "Semua tindakan", "Semua tindakan", "Semua tindakan", "Lihat/pulihkan"),
        ("Admin default", "Create/read/update; close/delete tertakluk config", "Create/read/update/delete; download tertakluk config", "Create/read/update", "Default tidak"),
        ("User default", "Create/read", "Create/read; download tertakluk config", "Create/read/update", "Default tidak"),
        ("Department permission", "Mengatasi role default mengikut bahagian", "Mengatasi role default mengikut bahagian", "Mengatasi role default mengikut bahagian", "Mengawal akses data dipadam"),
        ("User permission", "Override individu paling utama selepas superadmin", "Override individu", "Override individu", "Override individu"),
    ], widths=[3.4, 3.3, 3.8, 3.5, 3.0])

    doc.add_heading("10. Flowchart Sistem", level=1)
    for caption, image in [
        ("Flow 1: Pendaftaran, kelulusan dan log masuk", flow_auth),
        ("Flow 2: Folder, jilid dan dokumen", flow_folder),
        ("Flow 3: Cipta draf surat dan sokongan", flow_draft),
        ("Flow 4: Capaian, audit dan data dipadam", flow_admin),
    ]:
        p = doc.add_paragraph()
        p.style = doc.styles["Heading 2"]
        p.add_run(caption)
        doc.add_picture(str(image), width=Inches(6.8))
        doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER

    doc.add_heading("11. Aliran Pengguna End-to-End", level=1)
    doc.add_heading("11.1 Pengguna Baharu", level=2)
    for step in [
        "Pengguna membuka halaman daftar akaun.",
        "Pengguna mengisi nama, IC, emel rasmi MAINPP, bahagian dan kata laluan.",
        "Sistem menyemak IC dan emel unik serta domain MAINPP.",
        "Akaun disimpan sebagai pending.",
        "Admin atau superadmin meluluskan akaun.",
        "Pengguna log masuk dan masuk ke dashboard.",
    ]:
        add_number(doc, step)

    doc.add_heading("11.2 Pengurusan Folder dan Dokumen", level=2)
    for step in [
        "Pengguna dengan capaian folder.create membuka Tambah Folder.",
        "Untuk folder office, pengguna masukkan kod asas; sistem jana JLD automatik.",
        "Folder dipaparkan di Senarai Folder dan Dashboard.",
        "Pengguna membuka folder dan memilih Daftar Fail Baru.",
        "Sistem jana no rujukan berdasarkan folder_code dan file_numbering seterusnya.",
        "Dokumen serta lampiran disimpan dan boleh dicari semula melalui carian/dashboard.",
        "Jika folder perlu dihentikan, pengguna dengan capaian folder.close menutup folder.",
    ]:
        add_number(doc, step)

    doc.add_heading("11.3 Draf Surat Sokongan", level=2)
    for step in [
        "Pengguna memilih Cipta Draf Surat dan folder office aktif.",
        "Pengguna mengisi maklumat penerima, perkara, tarikh, kandungan dan tandatangan.",
        "Pengguna boleh menambah atau delete page draf surat.",
        "Jika perlu, pengguna memilih pegawai sokongan berdaftar.",
        "Sistem menjana dokumen, draf surat dan menghantar emel sokongan.",
        "Pegawai sokongan membuka pautan emel, log masuk dan memilih disokong atau ditolak.",
        "Status sokongan dipaparkan di Senarai Draf Surat, dashboard dan paparan surat.",
        "Surat dicetak dalam format A4 dengan teks penutup pada halaman akhir sebelum tandatangan.",
    ]:
        add_number(doc, step)

    doc.add_heading("12. Keperluan Bukan Fungsian", level=1)
    add_table(doc, ["Kategori", "Keperluan"], [
        ("Keselamatan", "Semua halaman operasi perlu requireLogin; password disimpan menggunakan password_hash; permission mesti disemak sebelum tindakan penting."),
        ("Audit", "Tindakan penting perlu direkodkan dalam activity_logs dengan user, role, IP dan user agent."),
        ("Kebolehgunaan", "Sidebar perlu kekal pada posisi menu semasa dan dashboard perlu menunjukkan status terkini folder/dokumen/draf."),
        ("Integriti data", "Gunakan transaksi ketika cipta dokumen/draf supaya documents dan letter_drafts kekal konsisten."),
        ("Emel", "SMTP perlu disediakan untuk reset password dan sokongan surat."),
        ("Cetakan", "Draf surat perlu berformat A4 dan sedia untuk print."),
        ("Fail upload", "Sistem perlu mengawal had saiz post/upload dan menyimpan lampiran dengan metadata asal."),
    ], widths=[4.2, 12.8])

    doc.add_heading("13. Pemerhatian Semakan Kod dan Database", level=1)
    observations = [
        ("Kekuatan", "Skema auto-ensure dalam db.php membantu memastikan column/table penting wujud semasa aplikasi dimuatkan."),
        ("Kekuatan", "Prepared statement digunakan secara meluas untuk operasi penting."),
        ("Kekuatan", "Soft delete dan restore memberi ruang kawalan data dipadam."),
        ("Kekuatan", "Permission matrix bahagian dan individu sudah menyokong keperluan capaian yang fleksibel."),
        ("Risiko", "mail_config.php mengandungi credential SMTP fallback dalam kod. Cadangan: pindahkan sepenuhnya ke environment variable dan rotate app password."),
        ("Risiko", "Cookie remember menyimpan password asal. Cadangan: gantikan dengan token remember-me yang dihash dalam database."),
        ("Risiko", "Beberapa fail memaparkan alert JavaScript untuk ralat; cadangan: standardkan flash message dan redirect yang konsisten."),
        ("Cadangan", "Tambah ujian automatik ringkas untuk format kod folder, jana JLD dan jana file_numbering."),
        ("Cadangan", "Tambah foreign key letter_drafts.document_id/folder_id jika belum dipasang supaya integriti relasi lebih kukuh."),
    ]
    add_table(doc, ["Jenis", "Pemerhatian"], observations, widths=[3.0, 14.0])

    doc.add_heading("14. Kriteria Penerimaan", level=1)
    for item in [
        "Pengguna baharu tidak boleh login sebelum status approved.",
        "Folder office pertama dijana sebagai JLD.1 dan JLD berikutnya hanya selepas folder semasa ditutup.",
        "Dokumen baharu dalam folder office memaparkan nombor rujukan bersama kurungan file_numbering.",
        "Folder closed tidak membenarkan tambah dokumen atau draf surat.",
        "Draf surat boleh mempunyai lebih daripada satu page dan page yang tidak digunakan boleh dipadam.",
        "Surat dengan sokongan menghantar emel kepada pegawai berdaftar dan dashboard menunjukkan status sokongan.",
        "Data dipadam tidak hilang terus daripada database dan boleh dipulihkan oleh pengguna yang diberi capaian.",
        "Audit log merekod tindakan penting pengguna.",
    ]:
        add_bullet(doc, item)

    doc.add_heading("15. Lampiran: Senarai Table Database", level=1)
    add_table(doc, ["Table", "Field Penting"], [
        ("activity_logs", "id, user_id, user_email, user_name, user_role, activity_type, activity_description, ip_address, user_agent, created_at"),
        ("custom_fields", "id, module, field_key, field_label, field_type, is_required, sort_order, is_active, field_options"),
        ("department_permissions", "id, department, module, action, is_allowed, updated_by, updated_at"),
        ("document_attachments", "id, document_id, file_path, original_name, created_at"),
        ("documents", "id, folder_id, ref_no, file_numbering, subject, doc_date, from_person, to_person, doc_type, custom_data, file_path, created_at, deleted_at, deleted_by"),
        ("folders", "id, folder_code, folder_name, department, folder_type, custom_data, created_at, deleted_at, deleted_by, closed_at, closed_by"),
        ("letter_drafts", "id, document_id, folder_id, department, ref_no, letter_date, subject, doc_type, recipient fields, letter_body, letter_pages, signatory fields, support fields"),
        ("password_resets", "id, email, token, expires_at, created_at"),
        ("role_permissions", "id, role_name, module, action, is_allowed, updated_by, updated_at"),
        ("user_permissions", "id, user_id, module, action, is_allowed, updated_by, updated_at"),
        ("users", "id, fullname, ic_number, email, department, profile_photo, password, status, role, registered_at, last_login"),
    ], widths=[4.2, 12.8])

    remove_trailing_empty_paragraphs(doc)
    doc.save(DOCX_PATH)
    print(DOCX_PATH)


if __name__ == "__main__":
    main()
