from pathlib import Path
from copy import deepcopy
from zipfile import ZipFile, ZIP_DEFLATED
from lxml import etree
from docx import Document
from docx.shared import Inches, Pt, Cm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.opc.constants import RELATIONSHIP_TYPE as RT

ROOT=Path(__file__).resolve().parents[1]
OUT=ROOT/'ESP_00VB_Livrables'
NAVY='16324F';TEAL='007D8A';GOLD='C99535';GRAY='526477'
ORG='Société d’accueil social et communautaire de ressources adaptées et personnelles'
def el(tag,attrs=None,text=None):
    x=OxmlElement(tag)
    if tag=='w:shd':x.set(qn('w:val'),'clear');x.set(qn('w:color'),'auto')
    for k,v in (attrs or {}).items():x.set(qn(k),str(v))
    if text is not None:x.text=text
    return x
def base(title='ESP 00VB — Techniques de bureautique',letter=False):
    d=Document();sec=d.sections[0];sec.page_width=Inches(8.5);sec.page_height=Inches(11)
    sec.top_margin=Cm(2.2);sec.bottom_margin=Cm(2);sec.left_margin=Cm(2.3);sec.right_margin=Cm(2.3)
    sec.header_distance=Cm(.9);sec.footer_distance=Cm(.9)
    st=d.styles['Normal'];st.font.name='Calibri';st.font.size=Pt(11);st.font.color.rgb=RGBColor.from_string(NAVY)
    st.paragraph_format.space_after=Pt(7);st.paragraph_format.line_spacing=1.12
    st.element.get_or_add_rPr().append(el('w:lang',{'w:val':'fr-CA'}))
    for name,size,color in [('Title',28,NAVY),('Subtitle',15,TEAL),('Heading 1',19,NAVY),('Heading 2',13,TEAL),('Heading 3',11,NAVY)]:
        s=d.styles[name];s.font.name='Calibri';s.font.size=Pt(size);s.font.color.rgb=RGBColor.from_string(color);s.font.bold=name!='Subtitle'
        s.paragraph_format.space_before=Pt(12);s.paragraph_format.space_after=Pt(8)
    hp=sec.header.paragraphs[0];hp.text=title;hp.style='Caption';hp.runs[0].font.size=Pt(8);hp.runs[0].font.color.rgb=RGBColor.from_string(GRAY)
    fp=sec.footer.paragraphs[0];fp.alignment=WD_ALIGN_PARAGRAPH.RIGHT
    fp.add_run('Dossier de démonstration  •  ');field(fp,'PAGE','1')
    for r in fp.runs:r.font.size=Pt(8)
    d.settings.element.insert_element_before(el('w:updateFields',{'w:val':'true'}),'w:compat','w:docVars','w:rsids','m:mathPr','w:themeFontLang')
    d.core_properties.title=title;d.core_properties.author='[Nom à compléter]';d.core_properties.language='fr-CA'
    return d
def p(d,text='',style=None):return d.add_paragraph(text,style)
def title(d,text,sub=None):
    d.add_heading(text,0)
    if sub:p(d,sub,'Subtitle')
def heading(d,text,level=1,mark=None):
    para=d.add_heading(text,level)
    if mark:bookmark(para,mark)
    return para
def bookmark(para,name):
    ident=str(sum(1 for _ in para.part.element.iter(qn('w:bookmarkStart')))+1)
    para._p.insert(1 if para._p.pPr is not None else 0,el('w:bookmarkStart',{'w:id':ident,'w:name':name}));para._p.append(el('w:bookmarkEnd',{'w:id':ident}))
def link(para,label,target=None,anchor=None):
    h=el('w:hyperlink',{'w:anchor':anchor}) if anchor else el('w:hyperlink',{'r:id':para.part.relate_to(target,RT.HYPERLINK,is_external=True)})
    r=el('w:r');pr=el('w:rPr');pr.append(el('w:color',{'w:val':TEAL}));pr.append(el('w:u',{'w:val':'single'}));r.append(pr);r.append(el('w:t',text=label));h.append(r);para._p.append(h);return h
def field(para,code,result='',nested=None):
    def runnode(node):r=el('w:r');r.append(node);para._p.append(r)
    runnode(el('w:fldChar',{'w:fldCharType':'begin','w:dirty':'true'}))
    it=el('w:instrText',text=code);it.set(qn('xml:space'),'preserve');runnode(it)
    if nested:
        for kind,val in nested:
            if kind=='field':field(para,val,'')
            else:
                it=el('w:instrText',text=val);it.set(qn('xml:space'),'preserve');runnode(it)
    runnode(el('w:fldChar',{'w:fldCharType':'separate'}));para.add_run(str(result));runnode(el('w:fldChar',{'w:fldCharType':'end'}))
def table(d,headers,rows,widths=None):
    t=d.add_table(rows=1,cols=len(headers));t.style='Table Grid';t.autofit=False
    for i,h in enumerate(headers):t.rows[0].cells[i].text=str(h)
    t.rows[0]._tr.get_or_add_trPr().append(el('w:tblHeader'))
    for row in rows:
        cells=t.add_row().cells
        for i,v in enumerate(row):cells[i].text=str(v)
    for ri,row in enumerate(t.rows):
        row._tr.get_or_add_trPr().append(el('w:cantSplit'))
        for ci,cell in enumerate(row.cells):
            if widths:cell.width=Cm(widths[ci])
            if ri==0:cell._tc.get_or_add_tcPr().append(el('w:shd',{'w:fill':NAVY}))
            elif ri%2==0:cell._tc.get_or_add_tcPr().append(el('w:shd',{'w:fill':'F0F5F8'}))
            cell.vertical_alignment=1
            for para in cell.paragraphs:
                para.paragraph_format.space_after=Pt(5);para.paragraph_format.space_before=Pt(5);para.paragraph_format.line_spacing=1.02
                for run in para.runs:
                    run.font.size=Pt(9)
                    if ri==0:run.font.bold=True;run.font.color.rgb=RGBColor(255,255,255)
    return t
def newpage(d,text,mark=None):d.add_page_break();heading(d,text,1,mark)
def save(d,name):
    path=OUT/name;d.save(path);return path
def as_template(source,target):
    with ZipFile(source) as zin,ZipFile(target,'w',ZIP_DEFLATED) as zout:
        for item in zin.infolist():
            data=zin.read(item.filename)
            if item.filename=='[Content_Types].xml':data=data.replace(b'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml',b'application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml')
            zout.writestr(item,data)
def merge_source(d):
    path=OUT/'03_Gestion_organisme.xlsx'
    mm=el('w:mailMerge')
    conn=f'Provider=Microsoft.ACE.OLEDB.12.0;Data Source={path};Extended Properties="HDR=YES;IMEX=1;Excel 12.0 Xml;"'
    for tag,val in [('mainDocumentType','formLetters'),('linkToQuery','true'),('dataType','spreadsheet'),('connectString',conn),('query',"SELECT * FROM `Fusion$` WHERE `Envoyer` = 'Oui'")]:mm.append(el('w:'+tag,{'w:val':val}))
    rid=d.part.part_related_by(RT.SETTINGS).relate_to(path.as_uri(),'http://schemas.openxmlformats.org/officeDocument/2006/relationships/mailMergeSource',is_external=True)
    mm.append(el('w:dataSource',{'r:id':rid}));mm.append(el('w:viewMergedData',{'w:val':'true'}));mm.append(el('w:activeRecord',{'w:val':'0'}))
    odso=el('w:odso');odso.append(el('w:udl',{'w:val':conn}));odso.append(el('w:table',{'w:val':'Fusion$'}));odso.append(el('w:src',{'r:id':rid}));odso.append(el('w:fHdr',{'w:val':'true'}));mm.append(odso)
    # In the schema, mailMerge precedes updateFields.
    d.settings.element.insert_element_before(mm,'w:revisionView','w:trackRevisions','w:documentProtection','w:defaultTabStop','w:characterSpacingControl','w:updateFields','w:compat')
def codebox(d,txt):
    para=p(d,txt);para.paragraph_format.space_after=Pt(10)
    para._p.get_or_add_pPr().insert_element_before(el('w:shd',{'w:fill':'F0F5F8'}),'w:tabs','w:spacing','w:ind','w:jc','w:rPr')
    for r in para.runs:r.font.name='Consolas';r.font.size=Pt(8.5)
    return para
