#!/usr/bin/env python3
"""
Apply template formatting rules to source thesis — SOURCE-as-base approach.
Uses the source document's page layout (preserving ~50-page count).
Applies:
  - UT Sans everywhere (all runs, paragraph marks, bullet symbols, table cells)
  - Heading 1: UT Sans 14pt Bold UPPERCASE Centered, page-break-before
  - Heading 2: UT Sans 12pt Bold
  - Body text: UT Sans 12pt, 1.5-line spacing, Justify, 0pt before/after
  - Lists:     UT Sans 12pt (numbering preserved)
  - Strips all embedded section breaks (continuous sectPr in paragraphs)
"""

import shutil, os, zipfile, io
from copy import deepcopy

from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement

FONT    = 'UT Sans'
BODY_PT = 12
H1_PT   = 14
H2_PT   = 12

SOURCE  = 'LUCRARE DIPLOMA ARANJATA IN PAG. CU TABELE.docx'
OUTPUT  = 'Nedelea_Valer_Licenta.docx'

MAIN_SECTIONS = {
    'INTRODUCERE', 'CUPRINS', 'BIBLIOGRAFIE',
    'ANEXE', 'CONCLUZII ȘI PROPUNERI', 'CONCLUZII',
}

# ── XML helpers ───────────────────────────────────────────────────────────────

def get_or_add(parent, tag):
    el = parent.find(qn(tag))
    if el is None:
        el = OxmlElement(tag)
        parent.append(el)
    return el

def set_rFonts(rPr):
    rf = get_or_add(rPr, 'w:rFonts')
    for attr in ('w:ascii', 'w:hAnsi', 'w:cs', 'w:eastAsia'):
        rf.set(qn(attr), FONT)

def set_sz(rPr, pt):
    val = str(int(pt * 2))
    for tag in ('w:sz', 'w:szCs'):
        el = get_or_add(rPr, tag)
        el.set(qn('w:val'), val)

def set_bold(rPr, on):
    b = rPr.find(qn('w:b'))
    if on:
        if b is None:
            rPr.append(OxmlElement('w:b'))
    else:
        if b is not None:
            rPr.remove(b)

def apply_run(r_el, pt=None, bold=None):
    """Apply UT Sans (and optionally size/bold) to a single w:r element."""
    rPr = r_el.find(qn('w:rPr'))
    if rPr is None:
        rPr = OxmlElement('w:rPr')
        r_el.insert(0, rPr)
    set_rFonts(rPr)
    if pt is not None:
        set_sz(rPr, pt)
    if bold is not None:
        set_bold(rPr, bold)

def apply_para_mark(p_el, pt=None, bold=None):
    """Apply UT Sans to paragraph mark (controls bullet/list symbol font)."""
    pPr = p_el.find(qn('w:pPr'))
    if pPr is None:
        pPr = OxmlElement('w:pPr')
        p_el.insert(0, pPr)
    rPr = pPr.find(qn('w:rPr'))
    if rPr is None:
        rPr = OxmlElement('w:rPr')
        pPr.append(rPr)
    set_rFonts(rPr)
    if pt is not None:
        set_sz(rPr, pt)
    if bold is not None:
        set_bold(rPr, bold)

def apply_all(container, pt=None, bold=None):
    """Apply UT Sans to every run and paragraph mark inside container."""
    for p_el in container.iter(qn('w:p')):
        apply_para_mark(p_el, pt=pt, bold=bold)
    for r_el in container.iter(qn('w:r')):
        apply_run(r_el, pt=pt, bold=bold)

# ── paragraph formatting helpers ─────────────────────────────────────────────

def get_pPr(p_el):
    pPr = p_el.find(qn('w:pPr'))
    if pPr is None:
        pPr = OxmlElement('w:pPr')
        p_el.insert(0, pPr)
    return pPr

def set_pStyle(pPr, style_id):
    el = pPr.find(qn('w:pStyle'))
    if el is None:
        el = OxmlElement('w:pStyle')
        pPr.insert(0, el)
    el.set(qn('w:val'), style_id)

def set_jc(pPr, val):
    el = get_or_add(pPr, 'w:jc')
    el.set(qn('w:val'), val)

def set_spacing(pPr, before_pt=0, after_pt=0, line=360, line_rule='auto'):
    sp = get_or_add(pPr, 'w:spacing')
    sp.set(qn('w:before'),   str(int(before_pt * 20)))
    sp.set(qn('w:after'),    str(int(after_pt  * 20)))
    sp.set(qn('w:line'),     str(line))
    sp.set(qn('w:lineRule'), line_rule)

def set_page_break_before(pPr):
    el = get_or_add(pPr, 'w:pageBreakBefore')
    el.set(qn('w:val'), '1')

def uppercase_runs(p_el):
    for t_el in p_el.iter(qn('w:t')):
        if t_el.text:
            t_el.text = t_el.text.upper()

def strip_sectpr(p_el):
    """Remove embedded continuous section break from paragraph pPr."""
    pPr = p_el.find(qn('w:pPr'))
    if pPr is not None:
        sp = pPr.find(qn('w:sectPr'))
        if sp is not None:
            pPr.remove(sp)

# ── classification ────────────────────────────────────────────────────────────

def classify(text, style_name, prev_kind):
    s = text.strip()
    u = s.upper()

    if not s:
        return 'empty'
    if '......' in s:
        return 'toc'

    # TOC chapter entries (Body Text style + "CAPITOLUL" text — no dots)
    if style_name == 'Body Text' and u.startswith('CAPITOLUL'):
        return 'toc'

    # Main chapter headings
    if u.startswith('CAPITOLUL'):
        return 'h1'
    if u in MAIN_SECTIONS or u.startswith('CONCLUZII') or u.startswith('BIBLIOGRAF') or u == 'ANEXE':
        return 'h1'

    # Split chapter title continuation (e.g. "HUNGARICUM S.R.L." after CAPITOLUL line)
    if prev_kind in ('h1', 'h1cont') and style_name in ('Normal', 'Heading 1', 'Body Text'):
        letters = [c for c in s if c.isalpha()]
        if letters and len(s) < 80:
            if sum(c.isupper() for c in letters) / len(letters) > 0.6:
                return 'h1cont'

    if style_name == 'Heading 1':
        return 'h2'
    if style_name == 'List Paragraph':
        return 'list'

    return 'body'

# ── main ──────────────────────────────────────────────────────────────────────

def main():
    # 1. Copy source → output (preserve page layout, images, tables, numbering)
    shutil.copy2(SOURCE, OUTPUT)
    print(f"Copied source → {OUTPUT}")

    doc = Document(OUTPUT)
    body = doc.element.body

    # 1b. Add Heading 2 style definition (source document lacks it)
    #     Build it using python-docx's registered element classes so the
    #     proxy wrapper is correct and iteration over styles doesn't break.
    from docx.oxml import parse_xml as _parse_xml
    styles_el = doc.part.styles._element
    h2_exists = any(
        s.get(qn('w:styleId')) == 'Heading2'
        for s in styles_el.findall(qn('w:style'))
    )
    if not h2_exists:
        W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
        h2_el = _parse_xml(
            f'<w:style xmlns:w="{W}" w:type="paragraph" w:styleId="Heading2">'
            f'<w:name w:val="heading 2"/>'
            f'<w:basedOn w:val="Normal"/>'
            f'<w:next w:val="Normal"/>'
            f'<w:pPr><w:spacing w:before="120" w:after="60"/></w:pPr>'
            f'<w:rPr>'
            f'<w:b/>'
            f'<w:rFonts w:ascii="{FONT}" w:hAnsi="{FONT}" w:cs="{FONT}" w:eastAsia="{FONT}"/>'
            f'<w:sz w:val="{int(H2_PT*2)}"/>'
            f'<w:szCs w:val="{int(H2_PT*2)}"/>'
            f'</w:rPr>'
            f'</w:style>'
        )
        styles_el.append(h2_el)
        print("Added Heading 2 style definition.")

    # 2. Process all body children (paragraphs + tables in order)
    prev_kind = 'empty'
    in_cover  = True
    para_count = 0

    from docx.text.paragraph import Paragraph

    for child in body:
        tag = child.tag.split('}')[-1] if '}' in child.tag else child.tag

        if tag == 'tbl':
            apply_all(child, pt=BODY_PT)
            prev_kind = 'table'

        elif tag == 'p':
            p_obj      = Paragraph(child, doc)
            text       = p_obj.text
            style_name = p_obj.style.name

            kind = classify(text, style_name, prev_kind)

            if kind == 'h1':
                in_cover = False

            pPr = get_pPr(child)
            strip_sectpr(child)   # remove continuous section breaks

            if kind == 'h1':
                set_pStyle(pPr, 'Heading1')
                set_page_break_before(pPr)
                set_jc(pPr, 'center')
                set_spacing(pPr, before_pt=12, after_pt=6, line=276)
                uppercase_runs(child)
                apply_all(child, pt=H1_PT, bold=True)

            elif kind == 'h1cont':
                set_pStyle(pPr, 'Heading1')
                set_jc(pPr, 'center')
                set_spacing(pPr, before_pt=0, after_pt=6, line=276)
                uppercase_runs(child)
                apply_all(child, pt=H1_PT, bold=True)

            elif kind == 'h2':
                set_pStyle(pPr, 'Heading2')
                set_spacing(pPr, before_pt=6, after_pt=3, line=276)
                apply_all(child, pt=H2_PT, bold=True)

            elif kind in ('list', 'toc'):
                apply_all(child, pt=BODY_PT)

            elif kind == 'empty':
                if style_name in ('Heading 1', 'Heading 2', 'Heading 3'):
                    set_pStyle(pPr, 'Normal')
                apply_all(child, pt=BODY_PT)

            elif in_cover:
                # Cover page: centered, UT Sans, no forced 1.5 spacing
                set_pStyle(pPr, 'Normal')
                set_jc(pPr, 'center')
                apply_all(child, pt=BODY_PT)

            else:  # body
                set_pStyle(pPr, 'Normal')
                set_jc(pPr, 'both')
                set_spacing(pPr, before_pt=0, after_pt=0, line=360)
                apply_all(child, pt=BODY_PT)

            prev_kind = kind
            para_count += 1

    print(f"Processed {para_count} paragraphs, {len(doc.tables)} tables.")

    doc.save(OUTPUT)
    print(f"Saved: {OUTPUT}")

if __name__ == '__main__':
    main()
