import os
import sys
import docx
import pandas as pd

# Ajout de QLineEdit et QLabel
from AnyQt.QtWidgets import QApplication, QPushButton, QLineEdit
from Orange.widgets import widget
from Orange.widgets.utils.signals import Input, Output
from Orange.data import Domain, StringVariable, Table, DiscreteVariable

if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
    from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
    from Orange.widgets.orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file
else:
    from orangecontrib.AAIT.utils.import_uic import uic
    from orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file

@apply_modification_from_python_file(filepath_original_widget=__file__)
class OWExtractTablesDocxToXlsx(widget.OWWidget):
    """
    Orange Widget qui extrait les tableaux de documents Word (.docx) et les sauvegarde
    en fichiers XLSX distincts (une table Word = un fichier XLSX).
    """
    name = "Docx to XLSX"
    description = "Extrait les tables de documents Word et les sauvegarde en XLSX, avec une option de division (split)."
    category = "AAIT - TOOLBOX"
    icon = "icons/extract_table.png"
    if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
        icon = "icons_dev/extract_table.png"
    gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owdocxtoxlsx.ui")
    want_control_area = False
    priority = 1005

    class Inputs:
        data = Input("Files Table", Table)

    class Outputs:
        data = Output("Processed Files Table", Table)
        status_data = Output("Status Table", Table)

    def __init__(self):
        super().__init__()
        try:
            uic.loadUi(self.gui, self)
        except Exception as e:
            self.warning(f"Impossible de charger le fichier UI. {e}")

            class DummyCheckbox:
                def stateChanged(self, *args): pass

            class DummyLineEdit:
                def text(self): return ""

                def textChanged(self, *args): pass

            self.checkBox_alpha_headers = DummyCheckbox()
            self.lineEdit_trigger_text = DummyLineEdit()
            self.gui = None

        self.pushButton_run = self.findChild(QPushButton, "pushButton_run")
        if self.pushButton_run:
            self.pushButton_run.clicked.connect(self.run)

        self.lineEdit_trigger_text = self.findChild(QLineEdit, "lineEdit_trigger_text")
        self.trigger_text = ""
        if self.lineEdit_trigger_text:
            self.lineEdit_trigger_text.textChanged.connect(self._update_trigger_text)

        self.data = None
        self.autorun = True
        self.processed_statuses = []
        self.use_alpha_headers = False
        if self.gui:
            self.checkBox_alpha_headers.stateChanged.connect(self._update_alpha_headers_state)

        self.post_initialized()

    def _update_alpha_headers_state(self, state):
        self.use_alpha_headers = bool(state)

    def _update_trigger_text(self, text):
        """Met à jour la variable du texte trigger à chaque changement."""
        self.trigger_text = text.strip()

    @Inputs.data
    def set_data(self, in_data: Table | None):
        self.data = in_data
        if self.autorun:
            self.run()

    def run(self):
        if self.data is None:
            self.Outputs.data.send(None)
            self.Outputs.status_data.send(None)
            return

        self.error("")
        try:
            self.data.domain["file_path"]
        except KeyError:
            self.error("Le tableau d'entrée doit contenir une colonne 'file_path'.")
            self.Outputs.data.send(None)
            self.Outputs.status_data.send(None)
            return

        self.progressBarInit()
        self.processed_statuses = []
        self.Outputs.status_data.send(None)

        if self.trigger_text:
            self.information(f"Utilisation du trigger de split : '{self.trigger_text}'")
        else:
            self.information("Aucun trigger de split défini. Export standard.")

        result_rows = self._process_files(self.data)

        output_domain = Domain([], metas=[
            StringVariable("src_path"),
            StringVariable("output_dir_path"),
            StringVariable("status")
        ])
        result_table = Table.from_list(output_domain, result_rows)
        self.Outputs.data.send(result_table)

        self.progressBarFinished()

    def _process_files(self, in_data: Table) -> list:
        result_rows = []
        file_paths = [str(x) for x in in_data.get_column("file_path")]
        total_files = len(file_paths)

        if not file_paths:
            return []

        for i, full_path in enumerate(file_paths):
            self.progressBarSet((i + 1) / total_files * 100)

            status_short = "ko"
            details = "traitement échoué"
            output_dir_path = ""

            if not full_path.lower().endswith('.docx'):
                status_short = "skipped"
                details = "Fichier ignoré : n'est pas un fichier .docx."
                output_dir_path = "N/A"
                self.processed_statuses.append([full_path, status_short, details])
                self._send_status_table()
                result_rows.append([full_path, output_dir_path, f"{status_short}: {details}"])
                QApplication.processEvents()
                continue

            try:
                tables_found, output_dir_path = self._extraire_et_convertir(full_path)

                if tables_found > 0:
                    status_short = "ok"
                    details_suffix = "séparée(s)" if self.trigger_text else "standard(s)"
                    details = f"{tables_found} table(s) {details_suffix} extraite(s) et convertie(s) en XLSX."
                else:
                    status_short = "ko"
                    details = "Aucune table valide trouvée."

            except FileNotFoundError:
                details = "Fichier non trouvé."
            except Exception as e:
                details = f"Une erreur inattendue est survenue : {e}"

            self.processed_statuses.append([full_path, status_short, details])
            self._send_status_table()

            result_rows.append([full_path, output_dir_path, f"{status_short}: {details}"])

            QApplication.processEvents()

        return result_rows

    # --- MODIFIÉ : Logique d'extraction simplifiée ---
    def _extraire_et_convertir(self, docx_path):
        """
        Extrait les tableaux d'un document Word.
        Sauvegarde les tables (splittées ou non) dans '..._tables_data'.
        """
        dir_name, file_name = os.path.split(docx_path)
        base_name, _ = os.path.splitext(file_name)

        # --- MODIFIÉ : Un seul dossier de sortie ---
        output_dir_main = os.path.join(dir_name, base_name + '_tables_data')
        os.makedirs(output_dir_main, exist_ok=True)

        doc = docx.Document(docx_path)
        total_tables_main_found = 0

        # Récupère le trigger et le met en minuscule (ou None)
        trigger_text_lower = self.trigger_text.lower() if self.trigger_text else None

        for i, table in enumerate(doc.tables):
            raw_data = []
            for row in table.rows:
                row_data = [cell.text.strip() for cell in row.cells]
                raw_data.append(row_data)

            if not raw_data or not any(row for row in raw_data):
                continue

            table_index = i + 1

            # --- MODIFIÉ : Section 1 (sauvegarde originale) supprimée ---

            # --- Traitement de la sortie principale (split ou standard) ---
            if trigger_text_lower:
                # Cas A : Un trigger est fourni, on split
                sub_tables_data = self._split_table_data(raw_data, trigger_text_lower)

                for j, sub_table_data in enumerate(sub_tables_data):
                    table_name = f"table_{table_index}_{chr(ord('a') + j)}"
                    df_split = self._create_dataframe(sub_table_data)

                    if df_split is not None and not df_split.empty:
                        self._save_sub_table(df_split, output_dir_main, table_name)
                        total_tables_main_found += 1

            else:
                # Cas B : Pas de trigger, comportement original
                table_name = f"table_{table_index}_a"  # Garde le suffixe _a pour cohérence
                df_main = self._create_dataframe(raw_data)

                if df_main is not None and not df_main.empty:
                    self._save_sub_table(df_main, output_dir_main, table_name)
                    total_tables_main_found += 1

        return total_tables_main_found, output_dir_main

    def _split_table_data(self, raw_data: list, trigger_text: str) -> list:
        """
        Découpe une table (liste de lignes) en sous-tables logiques.
        """
        data = [row for row in raw_data if row and any(cell.strip() for cell in row)]
        if not data:
            return []

        if self.use_alpha_headers:
            # Cas A : En-têtes alphabétiques
            sub_tables = []
            if not data:
                return []

            current_table = [data[0]]
            for row in data[1:]:
                if any(trigger_text in str(cell).lower() for cell in row):
                    # self.information(f"  [+] Déclencheur '{trigger_text}' détecté (mode alpha-headers). Division.")
                    if current_table:
                        sub_tables.append(current_table)
                    current_table = [row]
                else:
                    current_table.append(row)
            if current_table:
                sub_tables.append(current_table)
            return sub_tables

        else:
            # Cas B : Première ligne = en-tête
            if len(data) <= 1:
                return [data]

            headers = data[0]
            data_rows = data[1:]
            final_tables = []
            current_data_segment = []

            if data_rows:
                current_data_segment.append(data_rows[0])  # Ligne n+1 ne splite jamais

            for row in data_rows[1:]:  # Commence à n+2
                if any(trigger_text in str(cell).lower() for cell in row):
                    # self.information(f"  [+] Déclencheur '{trigger_text}' détecté (mode 1ere-ligne-header). Division.")
                    if current_data_segment:
                        final_tables.append([headers] + current_data_segment)
                    current_data_segment = [row]
                else:
                    current_data_segment.append(row)

            if current_data_segment:
                final_tables.append([headers] + current_data_segment)

            return final_tables

    def _create_dataframe(self, data):
        """
        Crée le DataFrame à partir des lignes brutes (d'une sous-table).
        """
        # Nettoyer les lignes vides
        data = [row for row in data if row and any(cell.strip() for cell in row)]
        if not data:
            return None

        max_cols = max(len(row) for row in data)
        data = [row + [''] * (max_cols - len(row)) for row in data]

        if self.use_alpha_headers:
            # Cas A : En-têtes alphabétiques. Toutes les lignes sont des données.
            headers = [chr(ord('A') + j) for j in range(max_cols)]
            df = pd.DataFrame(data, columns=headers)
        else:
            # Cas B : Première ligne comme en-tête.
            if len(data) == 1:
                # Si le segment n'a qu'une seule ligne, on utilise des en-têtes alphabétiques.
                headers = [chr(ord('A') + j) for j in range(max_cols)]
                df = pd.DataFrame(data, columns=headers)
            else:
                # Cas standard : première ligne = en-tête, reste = données.
                headers = data[0]
                data_rows = data[1:]
                min_cols = min(len(headers), max_cols)

                if not data_rows:
                    df = pd.DataFrame(columns=headers[:min_cols])
                else:
                    data_rows_adjusted = [row[:min_cols] for row in data_rows]
                    df = pd.DataFrame(data_rows_adjusted, columns=headers[:min_cols])

            df.columns = df.columns.astype(str)

        return df

    def _save_sub_table(self, df, output_dir, table_full_name):
        """Sauvegarde le DataFrame exclusivement en XLSX."""

        output_xlsx_path = os.path.join(output_dir, f"{table_full_name}.xlsx")
        try:
            df.to_excel(output_xlsx_path, index=False, engine='openpyxl')
        except Exception as e:
            self.warning(f"Impossible de sauvegarder la table '{table_full_name}' en format XLSX : {e}")

    def _send_status_table(self):
        domain = Domain([], metas=[
            StringVariable("src_path"),
            DiscreteVariable("status", values=["ok", "ko", "skipped"]),
            StringVariable("details")
        ])
        status_table = Table.from_list(domain, self.processed_statuses)
        self.Outputs.status_data.send(status_table)

    def post_initialized(self):
        pass


if __name__ == "__main__":
    app = QApplication(sys.argv)
    my_widget = OWExtractTablesDocxToXlsx()
    my_widget.show()
    app.exec()