#!/usr/bin/env python3
"""Dump compact d'un classeur .xlsx en texte lisible, une ligne par ligne de tableur.

But : préparer l'import de projets à faible coût. Chaque export projet ayant une
mise en page différente, la conversion en JSON n'est PAS automatisable — c'est une IA
(Claude/ChatGPT) qui lit et mappe (voir docs/CONSIGNE_IMPORT_PROJETS.md). Ce script ne
FAIT PAS le mapping : il se contente de sortir les cellules non vides sous une forme
minimale, pour que l'IA lise l'essentiel du classeur sans ingérer le binaire ni un dump
verbeux (donc peu de tokens).

Volontairement sans dépendance : un .xlsx est un zip de XML, on le lit avec la stdlib.
(À ne pas confondre avec scripts/extract_projects_excel.py, l'ancien mapping automatique
par openpyxl, abandonné car chaque fichier diffère.)

Usage :
    python3 scripts/dump_xlsx.py <fichier.xlsx> [--sheet NOM] [--max-rows N]
"""
from __future__ import annotations

import re
import sys
import zipfile
from xml.etree import ElementTree as ET

NS = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}"
RNS = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}"


def col_letter(ref: str) -> str:
    m = re.match(r"[A-Z]+", ref)
    return m.group(0) if m else ref


def load_shared_strings(z: zipfile.ZipFile) -> list[str]:
    try:
        root = ET.fromstring(z.read("xl/sharedStrings.xml"))
    except KeyError:
        return []
    out: list[str] = []
    for si in root.findall(f"{NS}si"):
        out.append("".join(t.text or "" for t in si.iter(f"{NS}t")))
    return out


def sheet_targets(z: zipfile.ZipFile) -> list[tuple[str, str]]:
    wb = ET.fromstring(z.read("xl/workbook.xml"))
    rels = ET.fromstring(z.read("xl/_rels/workbook.xml.rels"))
    rid_to_target = {r.get("Id"): r.get("Target") for r in rels}
    sheets: list[tuple[str, str]] = []
    for s in wb.find(f"{NS}sheets"):
        target = rid_to_target.get(s.get(f"{RNS}id"), "")
        target = target.lstrip("/") if target.startswith("/") else "xl/" + target.lstrip("/")
        sheets.append((s.get("name"), target))
    return sheets


def dump(path: str, only: str | None = None, max_rows: int | None = None) -> None:
    z = zipfile.ZipFile(path)
    shared = load_shared_strings(z)

    for name, target in sheet_targets(z):
        if only and name != only:
            continue
        print(f"===== ONGLET: {name} =====")
        try:
            sx = ET.fromstring(z.read(target))
        except KeyError:
            print("(introuvable)\n")
            continue
        sheet_data = sx.find(f"{NS}sheetData")
        if sheet_data is None:
            print("(vide)\n")
            continue

        count = 0
        for row in sheet_data.findall(f"{NS}row"):
            cells: list[str] = []
            for c in row.findall(f"{NS}c"):
                ref = c.get("r", "")
                t = c.get("t")
                v = c.find(f"{NS}v")
                inline = c.find(f"{NS}is")
                if t == "s" and v is not None and v.text is not None:
                    val = shared[int(v.text)]
                elif t == "inlineStr" and inline is not None:
                    val = "".join(x.text or "" for x in inline.iter(f"{NS}t"))
                elif v is not None:
                    val = v.text
                else:
                    val = None
                if val is not None and str(val).strip() != "":
                    cells.append(f"{col_letter(ref)}={val}")
            if cells:
                print(f"L{row.get('r', '?')}: " + " | ".join(cells))
                count += 1
                if max_rows and count >= max_rows:
                    print("... (tronqué)")
                    break
        if count == 0:
            print("(aucune cellule non vide)")
        print()


def main() -> int:
    args = sys.argv[1:]
    if not args:
        print(__doc__)
        return 2
    path = args[0]
    only = args[args.index("--sheet") + 1] if "--sheet" in args else None
    max_rows = int(args[args.index("--max-rows") + 1]) if "--max-rows" in args else None
    dump(path, only, max_rows)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
