import sys
import os
import shutil
import re
import zipfile
import json
import html
import textwrap
from unidecode import unidecode
from odf.opendocument import load
from odf import text, table, draw, style

# --- CONFIGURATION ---
VERSION = "V288"


# --- UTILITAIRES ---

def load_configuration(config_path):
    """Charge le fichier de configuration JSON."""
    default_config = {
        "mkdocs": {
            "site_name": "Documentation",
            "site_url": "",
            "site_description": "",
            "site_author": "",
            "repo_url": "",
            "repo_name": "GitHub"
        },
        "footer": "<footer>Pied de page</footer>",
        "code": {
            "language": "python",
            "style_keywords": ["code", "consolas", "source"],
            "default_language": "text",
            "detection_rules": {}
        },
        "document_title": {
            "style_names": ["Title", "Titre", "Titre principal"],
            "css": "font-size: 28px; font-weight: bold; margin-bottom: 1em;"
        },
        "debug": False,  # Valeur par défaut si absente du JSON
        "files_to_copy": [],
        "extra": {}
    }

    if not os.path.exists(config_path):
        return default_config

    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            user_config = json.load(f)
            if "mkdocs" in user_config: default_config["mkdocs"].update(user_config["mkdocs"])
            if "footer" in user_config: default_config["footer"] = user_config["footer"]
            if "code" in user_config: default_config["code"].update(user_config["code"])
            if "document_title" in user_config: default_config["document_title"].update(user_config["document_title"])
            # Lecture du paramètre debug depuis le JSON
            if "debug" in user_config: default_config["debug"] = user_config["debug"]
            if "files_to_copy" in user_config: default_config["files_to_copy"] = user_config["files_to_copy"]
            if "extra" in user_config: default_config["extra"] = user_config["extra"]
            return default_config
    except Exception as e:
        print(f"ERREUR lecture config: {e}. Utilisation des défauts.")
        return default_config


def create_directory(path):
    if not os.path.exists(path):
        os.makedirs(path)


def get_attribute_safe(node, attr_name):
    if hasattr(node, 'attributes'):
        for key, value in node.attributes.items():
            if isinstance(key, tuple):
                if key[1] == attr_name: return value
            elif key == attr_name:
                return value
    return None


def safe_int(val):
    try:
        return int(val)
    except:
        return None


# --- DÉTECTION DE LANGAGE & PROTECTION ---

def detect_language(code_content, code_config):
    if not code_content:
        return code_config.get('default_language', 'text')

    content_stripped = code_content.strip()

    if content_stripped.startswith("<?php"): return "php"
    if content_stripped.startswith("<?xml"): return "xml"

    rules = code_config.get('detection_rules', {})
    scores = {}
    for lang, keywords in rules.items():
        count = 0
        for k in keywords:
            if k in code_content: count += 1
        if count > 0: scores[lang] = count

    if scores: return max(scores, key=scores.get)

    if "function" in code_content or "var " in code_content: return "js"
    return code_config.get('default_language', 'text')


def get_fence(content):
    """Calcule la longueur des backticks nécessaire pour ne pas casser le code."""
    longest_backtick_seq = 0
    if content:
        matches = re.findall(r'`+', content)
        if matches:
            longest_backtick_seq = max(len(m) for m in matches)

    fence_len = max(3, longest_backtick_seq + 1)
    return "`" * fence_len


# --- STYLES & FORMATAGE ---

def build_readable_style_map(doc):
    readable_map = {}
    sources = []
    if doc.automaticstyles: sources.extend(doc.automaticstyles.childNodes)
    if doc.styles: sources.extend(doc.styles.childNodes)

    for s in sources:
        if s.tagName == 'style:style':
            internal = get_attribute_safe(s, 'name')
            display = get_attribute_safe(s, 'display-name')
            if internal:
                final = display if display else internal
                readable_map[internal] = final
    return readable_map


def build_style_inheritance_map(doc):
    inheritance = {}
    all_styles = []
    if doc.automaticstyles: all_styles.extend(doc.automaticstyles.childNodes)
    if doc.styles: all_styles.extend(doc.styles.childNodes)
    for s in all_styles:
        if s.tagName == 'style:style':
            name = get_attribute_safe(s, 'name')
            parent = get_attribute_safe(s, 'parent-style-name')
            if name and parent: inheritance[name] = parent
    return inheritance


def build_list_style_map(doc, code_keywords):
    list_map = {}
    all_list_styles = []

    def collect(source):
        if not source: return
        for child in source.childNodes:
            if child.tagName == 'text:list-style':
                all_list_styles.append(child)

    collect(doc.automaticstyles)
    collect(doc.styles)

    for s in all_list_styles:
        name = get_attribute_safe(s, 'name')
        if not name: continue

        has_numbering = False
        has_bullets = False

        for child in s.childNodes:
            if child.tagName == 'text:list-level-style-number':
                has_numbering = True
            elif child.tagName in ('text:list-level-style-bullet', 'text:list-level-style-image'):
                has_bullets = True

        if has_bullets:
            status = 'unordered'
        elif has_numbering:
            status = 'ordered'
        else:
            status = 'unordered'

        list_map[name] = status
    return list_map


def get_formatting_styles_with_inheritance(doc, inheritance_map):
    local_bold = set()
    local_underlined = set()
    local_bg_colors = {}
    local_italic = set()

    sources = []
    if doc.automaticstyles: sources.extend(doc.automaticstyles.childNodes)
    if doc.styles: sources.extend(doc.styles.childNodes)

    for s in sources:
        if s.tagName == 'style:style':
            name = get_attribute_safe(s, 'name')
            if not name: continue
            for child in s.childNodes:
                if child.tagName == 'style:text-properties':
                    fw = get_attribute_safe(child, 'font-weight')
                    if fw and (fw == 'bold' or fw == '700' or fw == '800' or fw == '900'):
                        local_bold.add(name)
                    us = get_attribute_safe(child, 'text-underline-style')
                    ut = get_attribute_safe(child, 'text-underline-type')
                    uw = get_attribute_safe(child, 'text-underline-width')
                    is_ul = (us and us != 'none') or (ut and ut != 'none') or (uw and uw != '0pt' and uw != '0cm')
                    if is_ul: local_underlined.add(name)

                    fs = get_attribute_safe(child, 'font-style')
                    if fs and (fs == 'italic' or fs == 'oblique'):
                        local_italic.add(name)

                    bg = get_attribute_safe(child, 'background-color')
                    if bg and bg != 'transparent':
                        bg_lower = bg.lower()
                        if bg_lower not in ['#ffffff', '#fff', 'white', 'none']:
                            local_bg_colors[name] = bg

    all_styles = set(inheritance_map.keys()).union(local_bold).union(local_underlined).union(
        local_bg_colors.keys()).union(local_italic)
    final_bold = set(local_bold)
    final_underlined = set(local_underlined)
    final_bg_colors = local_bg_colors.copy()
    final_italic = set(local_italic)

    changed = True
    while changed:
        changed = False
        for s in all_styles:
            p = inheritance_map.get(s)
            if p:
                if p in final_bold and s not in final_bold:
                    final_bold.add(s);
                    changed = True
                if p in final_underlined and s not in final_underlined:
                    final_underlined.add(s);
                    changed = True
                if p in final_bg_colors and s not in final_bg_colors:
                    final_bg_colors[s] = final_bg_colors[p]
                    changed = True
                if p in final_italic and s not in final_italic:
                    final_italic.add(s);
                    changed = True

    return final_bold, final_underlined, final_bg_colors, final_italic


# --- LOGIQUE LISTES CHAINÉES (Moteur OK10) ---

def build_list_chain_map_odfpy(doc):
    lists_db = {}
    all_lists = doc.text.getElementsByType(text.List)

    for lst in all_lists:
        lid = get_attribute_safe(lst, 'id')
        if not lid: continue

        prev = get_attribute_safe(lst, 'continue-list')
        explicit = safe_int(get_attribute_safe(lst, 'start-value'))

        count = 0
        for child in lst.childNodes:
            if child.tagName == 'text:list-item':
                count += 1

        lists_db[lid] = {
            'prev': prev,
            'explicit': explicit,
            'count': count,
            'computed': None
        }

    def resolve(cid, visited):
        if cid in visited or cid not in lists_db: return 1
        d = lists_db[cid]
        if d['computed'] is not None: return d['computed']

        visited.add(cid)

        if d['explicit'] is not None:
            val = d['explicit']
        elif d['prev']:
            pd = lists_db.get(d['prev'])
            val = resolve(d['prev'], visited) + (pd['count'] if pd else 0)
        else:
            val = 1

        d['computed'] = val
        return val

    for lid in lists_db: resolve(lid, set())
    return lists_db


def get_block_start_odfpy(node, list_chain_map):
    curr = node
    for _ in range(6):
        if not hasattr(curr, 'parentNode') or not curr.parentNode: break
        curr = curr.parentNode

        if curr.tagName == 'text:list-item':
            sv = safe_int(get_attribute_safe(curr, 'start-value'))
            if sv: return sv

            parent_list = curr.parentNode
            if parent_list and parent_list.tagName == 'text:list':
                lid = get_attribute_safe(parent_list, 'id')
                if lid and lid in list_chain_map:
                    val = list_chain_map[lid]['computed']
                    if val and val > 1: return val

                lsv = safe_int(get_attribute_safe(parent_list, 'start-value'))
                if lsv and lsv > 1: return lsv
    return 1


# --- TEXTE ---

def apply_formatting(txt, style_name, bold_styles, underlined_styles, highlight_colors, italic_styles, output_format):
    if not txt or output_format == "code_html": return txt

    is_bold = style_name and style_name in bold_styles
    is_underlined = style_name and style_name in underlined_styles
    bg_color = highlight_colors.get(style_name)
    is_italic = style_name and style_name in italic_styles

    if bg_color:
        txt = f'<mark style="background-color: {bg_color}">{txt}</mark>'

    if is_bold:
        if output_format == "md":
            txt = f"**{txt}**"
        elif output_format == "html":
            txt = f"<b>{txt}</b>"
    if is_italic:
        if output_format == "md":
            txt = f"*{txt}*"
        elif output_format == "html":
            txt = f"<i>{txt}</i>"
    if is_underlined: txt = f"<u>{txt}</u>"
    return txt


def has_image_child(node):
    for child in node.childNodes:
        if child.tagName == 'draw:image': return True
        if has_image_child(child): return True
    return False


def clean_text(node, escape=False, bold_styles=None, underlined_styles=None, highlight_colors=None, italic_styles=None,
               output_format="md"):
    if bold_styles is None: bold_styles = set()
    if underlined_styles is None: underlined_styles = set()
    if highlight_colors is None: highlight_colors = {}
    if italic_styles is None: italic_styles = set()

    txt = ""
    for child in node.childNodes:
        if child.tagName == 'draw:frame' and has_image_child(child): continue

        if child.nodeType == 3:
            data = str(child.data)
            if escape:
                data = html.escape(data)
                if output_format == "md": data = data.replace("*", "\\*").replace("_", "\\_")
            txt += data
        elif child.tagName == 'text:s':
            c = get_attribute_safe(child, 'c')
            txt += " " * (int(c) if c else 1)
        elif child.tagName == 'text:tab':
            txt += "    "
        elif child.tagName == 'text:line-break':
            txt += "<br>" if output_format == "html" else "\n"
        elif child.tagName == 'text:a':
            ls = get_attribute_safe(child, 'style-name')
            inner = clean_text(child, escape, bold_styles, underlined_styles, highlight_colors, italic_styles,
                               output_format)
            inner = apply_formatting(inner, ls, bold_styles, underlined_styles, highlight_colors, italic_styles,
                                     output_format)
            href = get_attribute_safe(child, 'href')
            if output_format == "md":
                txt += f"[{inner}]({href})"
            else:
                txt += f'<a href="{href}">{inner}</a>'
        elif child.tagName == 'text:span':
            ss = get_attribute_safe(child, 'style-name')
            inner = clean_text(child, escape, bold_styles, underlined_styles, highlight_colors, italic_styles,
                               output_format)
            inner = apply_formatting(inner, ss, bold_styles, underlined_styles, highlight_colors, italic_styles,
                                     output_format)
            txt += inner
        else:
            txt += clean_text(child, escape, bold_styles, underlined_styles, highlight_colors, italic_styles,
                              output_format)
    return txt


# --- IMAGES ---

def extract_images_from_zip(odt_path, output_dir):
    images_map = {}
    if not zipfile.is_zipfile(odt_path): return {}
    with zipfile.ZipFile(odt_path, 'r') as z:
        for file_info in z.infolist():
            if file_info.filename.startswith('Pictures/') and not file_info.filename.endswith('/'):
                original_name = os.path.basename(file_info.filename)
                safe_name = re.sub(r'[^a-zA-Z0-9._-]', '_', original_name)
                target_path = os.path.join(output_dir, safe_name)
                with open(target_path, 'wb') as f: f.write(z.read(file_info))
                images_map[file_info.filename] = f"images/{safe_name}"
    return images_map


def get_image_dimensions(image_node):
    parent = image_node.parentNode
    width = None;
    height = None
    if parent and parent.tagName == 'draw:frame':
        width = get_attribute_safe(parent, 'width')
        height = get_attribute_safe(parent, 'height')
    return width, height


def extract_image_tag_from_node(node, images_map):
    md = ""
    for img in node.getElementsByType(draw.Image):
        href = get_attribute_safe(img, 'href')
        if href:
            target = href if href in images_map else href.replace("./", "")
            if target in images_map:
                src = images_map[target]
                width, height = get_image_dimensions(img)
                style = ""
                if width: style += f"width:{width};"
                if height: style += f"height:{height};"
                if not style: style = "max-width:100%;height:auto;"
                md += f'\n<img src="{src}" style="{style}" alt="Image" />\n'
    return md


def get_images_in_node_as_html(node, images_map):
    html_out = ""
    for img in node.getElementsByType(draw.Image):
        href = get_attribute_safe(img, 'href')
        if href:
            target = href if href in images_map else href.replace("./", "")
            if target in images_map:
                src = images_map[target]
                width, height = get_image_dimensions(img)
                style = "display:inline-block; margin:4px;"
                if width: style += f"width:{width};"
                if height:
                    style += f"height:{height};"
                else:
                    style += "max-width:100%;height:auto;"
                html_out += f'<img src="{src}" style="{style}" />'
    return html_out


# --- IDENTIFICATION ---

def get_readable_style_name(internal, readable_map, inheritance_map):
    curr = internal
    for _ in range(10):
        if not curr: break
        if curr in readable_map: return readable_map[curr]
        curr = inheritance_map.get(curr)
    return internal


def get_heading_level(node, readable_map, inheritance_map, bold_styles):
    if node.tagName == 'text:h':
        lvl = get_attribute_safe(node, 'outline-level')
        return int(lvl) if lvl else 1

    if node.tagName == 'text:p':
        style_name = get_attribute_safe(node, 'style-name')
        if not style_name: return 0
        txt = clean_text(node).strip()
        length = len(txt)

        if length > 150: return 0

        readable = get_readable_style_name(style_name, readable_map, inheritance_map)
        s_clean = unidecode(readable).lower() if readable else ""

        forbidden_styles = ['normal', 'standard', 'text body', 'corps de texte', 'default']
        if any(x == s_clean or x in s_clean for x in forbidden_styles): return 0

        match = re.search(r"^(titre|heading|h|chapitre)\s*(\d+)", s_clean)
        if match: return int(match.group(2))

        outline = get_attribute_safe(node, 'outline-level')
        if outline: return int(outline)
    return 0


def is_code_paragraph(node, readable_map, inheritance_map, style_keywords):
    if node.tagName != 'text:p': return False
    curr = get_attribute_safe(node, 'style-name')
    targets = set(style_keywords)
    for _ in range(10):
        if not curr: break
        readable = get_readable_style_name(curr, readable_map, inheritance_map)
        s_clean = unidecode(readable).lower()
        if any(t in s_clean for t in targets): return True
        curr = inheritance_map.get(curr)
    return False


# --- FLUX PRINCIPAL ---

def stream_nodes_context(node, readable_map, inheritance_map, bold_styles, list_style_map, list_level=0,
                         list_type='unordered'):
    if node.tagName in ('office:text', 'text:section', 'draw:frame', 'draw:text-box'):
        for child in node.childNodes:
            yield from stream_nodes_context(child, readable_map, inheritance_map, bold_styles, list_style_map,
                                            list_level, list_type)
        return

    if node.tagName == 'table:table':
        yield (node, list_level, list_type)
        return

    if node.tagName == 'text:p' and has_nested_table(node):
        buffer = []
        for child in node.childNodes:
            if child.tagName in ('draw:frame', 'draw:text-box'):
                if buffer:
                    p = text.P();
                    p.childNodes = buffer
                    yield (p, list_level, list_type)
                    buffer = []
                yield from stream_nodes_context(child, readable_map, inheritance_map, bold_styles, list_style_map,
                                                list_level, list_type)
            else:
                buffer.append(child)
        if buffer: p = text.P(); p.childNodes = buffer; yield (p, list_level, list_type)
        return

    if node.tagName == 'text:list':
        style_name = get_attribute_safe(node, 'style-name')
        new_type = 'unordered'
        if style_name and style_name in list_style_map:
            new_type = list_style_map[style_name]

        new_level = list_level + 1

        for child in node.childNodes:
            if child.tagName == 'text:list-item':
                for item_child in child.childNodes:
                    if item_child.tagName == 'text:list':
                        yield from stream_nodes_context(item_child, readable_map, inheritance_map, bold_styles,
                                                        list_style_map, new_level, new_type)
                    else:
                        yield from stream_nodes_context(item_child, readable_map, inheritance_map, bold_styles,
                                                        list_style_map, new_level, new_type)
        return

    yield (node, list_level, list_type)


def has_nested_table(node):
    if node.tagName == 'table:table': return True
    for child in node.childNodes:
        if has_nested_table(child): return True
    return False


def get_all_rows(node):
    rows = []
    if node.tagName == 'table:table-row':
        rows.append(node)
    elif node.tagName in ('table:table', 'table:table-header-rows', 'table:table-footer-rows', 'table:table-row-group'):
        for child in node.childNodes: rows.extend(get_all_rows(child))
    return rows


# --- FORMATAGE ---

def process_table_to_html(table_node, images_map, bold_styles, underlined_styles, highlight_colors, italic_styles,
                          readable_map, inheritance_map, config):
    html_out = ['<table class="odt-table" style="border-collapse: collapse; width: 100%; margin-bottom: 1em;">']
    all_rows = get_all_rows(table_node)
    if not all_rows: return ""

    code_lang = config['code'].get('language', 'text')
    style_keywords = config['code']['style_keywords']

    for row in all_rows:
        html_out.append('<tr>')
        for cell in row.childNodes:
            if cell.tagName == 'table:covered-table-cell': continue
            if cell.tagName == 'table:table-cell':
                colspan = get_attribute_safe(cell, 'number-columns-spanned')
                rowspan = get_attribute_safe(cell, 'number-rows-spanned')
                attrs = ' style="border: 1px solid #ddd; padding: 8px; vertical-align: top;"'
                if colspan: attrs += f' colspan="{colspan}"'
                if rowspan: attrs += f' rowspan="{rowspan}"'
                html_out.append(f'<td{attrs}>')

                cell_buffer = []
                code_buffer = []
                code_start_line = 1

                def flush_code():
                    nonlocal code_start_line
                    if code_buffer:
                        full_code = "\n".join(code_buffer)
                        lang = detect_language(full_code, config['code'])
                        fence = get_fence(full_code)
                        cell_buffer.append(
                            f'<div markdown="block">\n{fence}{lang} linenums="{code_start_line}"\n{full_code}\n{fence}\n</div>')
                        code_buffer.clear()
                        code_start_line = 1

                def extract_cell_content(n, prefix=""):
                    nonlocal code_start_line
                    if n.tagName == 'text:p':
                        is_code = is_code_paragraph(n, readable_map, inheritance_map, style_keywords)
                        if is_code:
                            if not code_buffer:
                                ln = get_attribute_safe(n, 'line-number')
                                code_start_line = int(ln) if ln else 1
                            code_txt = clean_text(n, escape=False)
                            code_buffer.append(code_txt)
                        else:
                            flush_code()
                            p_style = get_attribute_safe(n, 'style-name')
                            txt = clean_text(n, escape=True, bold_styles=bold_styles,
                                             underlined_styles=underlined_styles, highlight_colors=highlight_colors,
                                             italic_styles=italic_styles, output_format="html")
                            txt = apply_formatting(txt, p_style, bold_styles, underlined_styles, highlight_colors,
                                                   italic_styles, output_format="html")
                            img = get_images_in_node_as_html(n, images_map)
                            if txt.strip(): cell_buffer.append(f'<p style="margin:0;">{prefix}{txt}</p>')
                            if img: cell_buffer.append(img)

                    elif n.tagName == 'text:list':
                        flush_code()
                        for item in n.childNodes:
                            if item.tagName == 'text:list-item':
                                for c in item.childNodes: extract_cell_content(c, prefix="&bull; ")
                        flush_code()
                    elif n.tagName == 'table:table':
                        flush_code()
                        cell_buffer.append(
                            process_table_to_html(n, images_map, bold_styles, underlined_styles, highlight_colors,
                                                  italic_styles, readable_map, inheritance_map, config))
                    else:
                        for c in n.childNodes: extract_cell_content(c, prefix)

                for child in cell.childNodes: extract_cell_content(child)
                flush_code()
                html_out.append("".join(cell_buffer) if cell_buffer else "&nbsp;")
                html_out.append('</td>')
        html_out.append('</tr>')
    html_out.append('</table>')
    return "".join(html_out)


# --- ASSETS ---

def generate_focus_assets(docs_dir):
    js_dir = os.path.join(docs_dir, "javascripts")
    css_dir = os.path.join(docs_dir, "stylesheets")
    create_directory(js_dir)
    create_directory(css_dir)
    with open(os.path.join(css_dir, "focus.css"), "w", encoding="utf-8") as f:
        f.write(
            "body.md-focus .md-sidebar--primary { display: none; } body.md-focus .md-sidebar--secondary { display: none; } body.md-focus .md-content { max-width: 95%; margin: 0 auto; } .focus-toggle-btn { cursor: pointer; margin-left: 0.5rem; } .odt-table img { max-width: 100%; height: auto; }")
    with open(os.path.join(js_dir, "focus.js"), "w", encoding="utf-8") as f:
        f.write(
            'document.addEventListener("DOMContentLoaded",function(){if(document.querySelector(".focus-toggle-btn"))return;var e=document.createElement("label");e.className="md-icon md-header__button focus-toggle-btn",e.title="Mode Focus",e.innerHTML=\'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3M14 5v2h3v3h2V5h-5Z"></path></svg>\';var t=document.querySelector(".md-header__inner");t&&t.appendChild(e),e.addEventListener("click",function(){document.body.classList.toggle("md-focus"),document.body.classList.contains("md-focus")?e.innerHTML=\'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2m3-8H5v2h5V5H8v3m6 11h2v-3h3v-2h-5v5m2-11V5h-2v5h5V8h-3Z"></path></svg>\':e.innerHTML=\'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5m12 7h-3v2h5v-5h-2v3M14 5v2h3v3h2V5h-5Z"></path></svg>\'})});')


def generate_mkdocs_yml(nav_structure, config_data):
    mk = config_data['mkdocs']

    config = textwrap.dedent(f"""\
        site_name: "{mk.get('site_name', 'My Site')}"
        site_url: "{mk.get('site_url', '')}"
        site_description: "{mk.get('site_description', '')}"
        site_author: "{mk.get('site_author', '')}"
        use_directory_urls: false
        repo_url: "{mk.get('repo_url', '')}"
        repo_name: "{mk.get('repo_name', 'GitHub')}"

        theme:
          name: material
          custom_dir: overrides
          features: [navigation.sections, navigation.indexes, toc.integrate, navigation.top]
          palette:
            - media: "(prefers-color-scheme: light)"
              scheme: default
              primary: teal
              accent: purple
              toggle:
                icon: material/brightness-7
                name: Passer au mode sombre
            - media: "(prefers-color-scheme: dark)"
              scheme: slate
              primary: teal
              accent: purple
              toggle:
                icon: material/brightness-4
                name: Passer au mode clair
        markdown_extensions: 
          - admonition
          - attr_list
          - pymdownx.superfences
          - pymdownx.mark
          - pymdownx.highlight:
              anchor_linenums: true
              linenums: true
        extra_javascript: [javascripts/focus.js]
        extra_css: [stylesheets/focus.css]
        nav:
        """)
    for item in nav_structure:
        for k, v in item.items():
            safe_key = json.dumps(k, ensure_ascii=False)
            config += f"  - {safe_key}: {v}\n"

    if 'extra' in config_data:
        config += "\nextra:\n"
        if 'analytics' in config_data['extra']:
            an = config_data['extra']['analytics']
            config += f"  analytics:\n    provider: {an.get('provider', 'google')}\n    property: {an.get('property', '')}\n"

    with open("mkdocs.yml", "w", encoding="utf-8") as f:
        f.write(config)


# --- MAIN ---

def convert_odt_to_site(odt_file, config_file):
    print(f"--- ODT to MkDocs Converter {VERSION} ---")
    if not os.path.exists(odt_file): return print(f"Erreur: {odt_file} introuvable.")

    config = load_configuration(config_file)
    is_debug = config.get('debug', False)

    print(f"Traitement de {odt_file} avec config {config_file}...")

    if os.path.exists("docs"): shutil.rmtree("docs", ignore_errors=True)
    create_directory("docs")
    create_directory("docs/images")
    create_directory("overrides/partials/integrations")

    files_list = config.get('files_to_copy', [])
    for f in files_list:
        if os.path.exists(f):
            try:
                shutil.copy(f, os.path.join("docs", f))
                print(f"Copié : {f}")
            except Exception as e:
                print(f"Erreur copie {f}: {e}")
        else:
            print(f"Info: Fichier '{f}' non trouvé (ignoré).")

    generate_focus_assets("docs")
    footer_content = config.get('footer', '<footer>Pied de page</footer>')
    with open("overrides/partials/footer.html", "w", encoding="utf-8") as f:
        f.write(footer_content)

    analytics_content = """{% if config.extra.analytics %}
  <script async src="https://www.googletagmanager.com/gtag/js?id={{ config.extra.analytics.property }}"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    var siteName = "{{ config.site_name | replace('\\"', '\\\\\"') }}";
    gtag('config', '{{ config.extra.analytics.property }}', {
        'page_title': siteName
    });
  </script>
{% endif %}"""

    with open("overrides/partials/integrations/analytics.html", "w", encoding="utf-8") as f:
        f.write(analytics_content)
    with open("overrides/partials/analytics.html", "w", encoding="utf-8") as f:
        f.write(analytics_content)

    doc = load(odt_file)
    images_map = extract_images_from_zip(odt_file, "docs/images")
    readable_map = build_readable_style_map(doc)
    inheritance_map = build_style_inheritance_map(doc)
    style_keywords = config['code']['style_keywords']
    list_style_map = build_list_style_map(doc, style_keywords)
    bold_styles, underlined_styles, highlight_colors, italic_styles = get_formatting_styles_with_inheritance(doc,
                                                                                                             inheritance_map)
    # AJOUT V276 : Moteur OK10
    list_chain_map = build_list_chain_map_odfpy(doc)

    nav = [{"Accueil": "index.md"}]
    current_file = open("docs/index.md", "w", encoding="utf-8")
    counters = [0] * 10
    code_buffer = []
    code_start_line = 1

    print("Génération Markdown...")

    # PREPARATION CONFIG TITRE
    doc_title_config = config.get('document_title', {})
    target_styles = [unidecode(s).lower().strip() for s in doc_title_config.get('style_names', [])]
    custom_css = doc_title_config.get('css', '')

    found_first_h1 = False  # Tracker pour debug

    previous_was_list = False

    # --- BOUCLE PRINCIPALE ---
    for node, list_level, list_type in stream_nodes_context(doc.text, readable_map, inheritance_map, bold_styles,
                                                            list_style_map):

        if previous_was_list and list_level == 0:
            current_file.write("\n")
        previous_was_list = (list_level > 0)

        # 1. TABLEAU
        if node.tagName == 'table:table':
            current_file.write(
                process_table_to_html(node, images_map, bold_styles, underlined_styles, highlight_colors, italic_styles,
                                      readable_map, inheritance_map,
                                      config) + "\n\n")
            continue

        # --- DEBUG BLOCK (V289: USE CONFIG) ---
        if is_debug and not found_first_h1 and node.tagName in ('text:p', 'text:h'):
            p_style = get_attribute_safe(node, 'style-name')
            readable = get_readable_style_name(p_style, readable_map, inheritance_map)
            s_clean = unidecode(readable).lower().strip() if readable else ""
            txt_preview = clean_text(node)[:40]
            print(f"[DEBUG PRE-H1] Style='{readable}' (Clean='{s_clean}') | Texte='{txt_preview}...'")
        # --------------------------

        # ---------------------------------------------------------
        # 2. DETECTION TITRE DOCUMENT (PRIORITAIRE)
        # ---------------------------------------------------------
        if node.tagName in ('text:p', 'text:h'):
            p_style = get_attribute_safe(node, 'style-name')
            readable = get_readable_style_name(p_style, readable_map, inheritance_map)
            s_clean = unidecode(readable).lower().strip() if readable else ""

            # Verification configuration
            if s_clean in target_styles:
                txt = clean_text(node, escape=True, bold_styles=bold_styles, underlined_styles=underlined_styles,
                                 highlight_colors=highlight_colors, italic_styles=italic_styles, output_format="md")
                if txt.strip():
                    if is_debug: print(f">>> TITRE DOCUMENT DETECTÉ : {txt}")
                    current_file.write(f'<p style="{custom_css}">{txt}</p>\n\n')
                continue
        # ---------------------------------------------------------

        # 3. TITRES (Chapitres normaux)
        heading_level = 0
        if node.tagName in ('text:p', 'text:h'):
            heading_level = get_heading_level(node, readable_map, inheritance_map, bold_styles)

        if heading_level > 0:
            if heading_level == 1: found_first_h1 = True  # STOP DEBUG

            if code_buffer:
                full_code = "\n".join(code_buffer)
                lang = detect_language(full_code, config['code'])
                fence = get_fence(full_code)
                indent_str = "    " * list_level
                indented_fence = f"{indent_str}{fence}"
                indented_body = "\n".join([f"{indent_str}{line}" for line in code_buffer])
                current_file.write(
                    f"\n{indented_fence}{lang} linenums=\"{code_start_line}\"\n{indented_body}\n{indented_fence}\n\n")
                code_buffer = []
                code_start_line = 1

            title_text = clean_text(node, escape=True, bold_styles=bold_styles, underlined_styles=underlined_styles,
                                    highlight_colors=highlight_colors, italic_styles=italic_styles,
                                    output_format="md").strip()
            print(f">>> CHAPITRE: {title_text} (Niv {heading_level})")

            counters[heading_level - 1] += 1
            for i in range(heading_level, 10): counters[i] = 0

            if heading_level == 1:
                num_str = str(counters[0])
            else:
                num_str = ".".join(str(counters[i]) for i in range(heading_level))

            full_title = f"{num_str}. {title_text}"

            if heading_level == 1:
                current_file.close()
                fname = re.sub(r'[^a-z0-9\-]', '', unidecode(title_text.lower().replace(" ", "-"))) + ".md"
                current_file = open(f"docs/{fname}", "w", encoding="utf-8")
                current_file.write(f"# {full_title}\n\n")
                nav.append({full_title: fname})
            else:
                current_file.write(f"{'#' * heading_level} {full_title}\n\n")
            continue

        # 4. LIST ITEM ORPHELIN
        if node.tagName == 'text:list-item':
            txt_parts = []
            for child in node.childNodes:
                if child.tagName == 'text:p':
                    txt_parts.append(
                        clean_text(child, escape=True, bold_styles=bold_styles, underlined_styles=underlined_styles,
                                   highlight_colors=highlight_colors, italic_styles=italic_styles, output_format="md"))
            full_txt = " ".join(txt_parts).strip()
            if full_txt:
                current_file.write(f"* {full_txt}\n")
            continue

        # 5. CODE
        if node.tagName == 'text:p' and is_code_paragraph(node, readable_map, inheritance_map, style_keywords):
            if not code_buffer:
                val_list = get_block_start_odfpy(node, list_chain_map)
                val_para = get_attribute_safe(node, 'line-number')
                val_para = int(val_para) if val_para else 0
                if val_list > 1:
                    code_start_line = val_list
                elif val_para > 0:
                    code_start_line = val_para
                else:
                    code_start_line = 1

            code_buffer.append(clean_text(node))
            continue

        if code_buffer:
            full_code = "\n".join(code_buffer)
            lang = detect_language(full_code, config['code'])
            indent_str = "    " * list_level
            fence = get_fence(full_code)
            indented_fence = f"{indent_str}{fence}"
            indented_body = "\n".join([f"{indent_str}{line}" for line in code_buffer])
            current_file.write(
                f"\n{indented_fence}{lang} linenums=\"{code_start_line}\"\n{indented_body}\n{indented_fence}\n\n")
            code_buffer = []
            code_start_line = 1

        # 6. TEXTE NORMAL
        if node.tagName in ('text:p', 'text:h'):
            p_style = get_attribute_safe(node, 'style-name')
            txt = clean_text(node, escape=True, bold_styles=bold_styles, underlined_styles=underlined_styles,
                             highlight_colors=highlight_colors, italic_styles=italic_styles,
                             output_format="md")
            txt = apply_formatting(txt, p_style, bold_styles, underlined_styles, highlight_colors, italic_styles,
                                   output_format="md")
            img = extract_image_tag_from_node(node, images_map)

            if txt.strip():
                if list_level > 0:
                    indent = "    " * (list_level - 1)
                    marker = "1. " if list_type == 'ordered' else "* "
                    current_file.write(f"{indent}{marker}{txt}\n")
                else:
                    current_file.write(f"{txt}\n\n")

            if img: current_file.write(f"{img}\n\n")

    if code_buffer:
        full = "\n".join(code_buffer)
        lang = detect_language(full, config['code'])
        indent_str = "    " * list_level
        fence = get_fence(full)
        indented_fence = f"{indent_str}{fence}"
        indented_body = "\n".join([f"{indent_str}{line}" for line in code_buffer])
        current_file.write(
            f"\n{indented_fence}{lang} linenums=\"{code_start_line}\"\n{indented_body}\n{indented_fence}\n")

    current_file.close()
    generate_mkdocs_yml(nav, config)
    print("Terminé.")


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python convert.py <fichier.odt> <config.json>")
    else:
        convert_odt_to_site(sys.argv[1], sys.argv[2])