"""
Main window: directory picker, file list, rule selector + stack, preview table, Apply.
"""
import os
from pathlib import Path

from PyQt6.QtWidgets import (
    QMainWindow,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QSplitter,
    QLabel,
    QPushButton,
    QLineEdit,
    QTableWidget,
    QTableWidgetItem,
    QFileDialog,
    QScrollArea,
    QGroupBox,
    QMessageBox,
    QHeaderView,
    QAbstractItemView,
    QFrame,
)
from PyQt6.QtCore import Qt, QDir, QItemSelectionModel
from PyQt6.QtGui import QFont, QColor

from engine.pipeline import compute_preview, perform_renames, save_undo_log, load_undo_log, perform_undo
from engine.rules import Rule
from .rule_widgets import (
    ReplaceRuleWidget,
    RegexRuleWidget,
    InsertRuleWidget,
    RemoveRuleWidget,
    CaseRuleWidget,
    NumberingRuleWidget,
    EpisodeRenumberRuleWidget,
    PrefixSuffixRuleWidget,
    CsvMappingRuleWidget,
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("HSRename")
        self.resize(1000, 700)
        self._base_dir = ""
        self._file_names: list[str] = []
        self._preview_by_orig: dict[str, str] = {}  # original name -> new name for selection preview
        self._setup_ui()
        self._refresh_preview()

    def _setup_ui(self):
        central = QWidget()
        self.setCentralWidget(central)
        layout = QVBoxLayout(central)

        # Top: directory bar
        dir_layout = QHBoxLayout()
        self.dir_edit = QLineEdit()
        self.dir_edit.setPlaceholderText("Select a folder or enter path...")
        self.dir_edit.textChanged.connect(self._on_dir_changed)
        browse_btn = QPushButton("Browse…")
        browse_btn.clicked.connect(self._browse)
        dir_layout.addWidget(QLabel("Folder:"))
        dir_layout.addWidget(self.dir_edit, 1)
        dir_layout.addWidget(browse_btn)
        layout.addLayout(dir_layout)

        # Split: left = rules, right = file list + preview
        split = QSplitter(Qt.Orientation.Horizontal)

        # Left: all rule panels at once (enable and configure multiple rules)
        left = QWidget()
        left_layout = QVBoxLayout(left)
        left_layout.addWidget(QLabel("Rename rules (applied in order; enable and configure any combination):"))

        self._rule_widgets = [
            ReplaceRuleWidget(),
            RegexRuleWidget(),
            InsertRuleWidget(),
            RemoveRuleWidget(),
            CaseRuleWidget(),
            NumberingRuleWidget(),
            EpisodeRenumberRuleWidget(),
            PrefixSuffixRuleWidget(),
            CsvMappingRuleWidget(),
        ]
        rule_titles = [
            "1. Replace",
            "2. Regex",
            "3. Insert",
            "4. Remove",
            "5. Case",
            "6. Numbering",
            "7. Episode renumber",
            "8. Prefix / Suffix",
            "9. CSV mapping",
        ]
        for title, w in zip(rule_titles, self._rule_widgets):
            w.enabled_cb.toggled.connect(self._refresh_preview)  # always refresh when checkbox toggled
            w.ruleChanged.connect(self._on_rule_changed)  # refresh only when enabled rule’s options change
            g = QGroupBox(title)
            g_layout = QVBoxLayout(g)
            g_layout.setContentsMargins(8, 12, 8, 8)
            g_layout.addWidget(w)
            left_layout.addWidget(g)
        left_layout.addStretch(1)

        scroll_left = QScrollArea()
        scroll_left.setWidget(left)
        scroll_left.setWidgetResizable(True)
        scroll_left.setFrameShape(QFrame.Shape.NoFrame)
        scroll_left.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        split.addWidget(scroll_left)

        # Right: file list + preview table
        right = QWidget()
        right_layout = QVBoxLayout(right)
        right_layout.addWidget(QLabel("Preview (Original → New name):"))
        self.preview_table = QTableWidget()
        self.preview_table.setColumnCount(3)
        self.preview_table.setHorizontalHeaderLabels(["Original name", "New name", "File type"])
        self.preview_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
        self.preview_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
        self.preview_table.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
        self.preview_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
        self.preview_table.setSortingEnabled(True)
        self.preview_table.itemSelectionChanged.connect(self._on_preview_selection_changed)
        right_layout.addWidget(self.preview_table, 1)
        self.preview_status = QLabel("Add a folder to see files.")
        right_layout.addWidget(self.preview_status)

        btn_layout = QHBoxLayout()
        apply_btn = QPushButton("Apply renames")
        apply_btn.setMinimumHeight(36)
        apply_btn.clicked.connect(self._apply_renames)
        undo_btn = QPushButton("Undo last rename")
        undo_btn.setMinimumHeight(36)
        undo_btn.clicked.connect(self._undo_renames)
        btn_layout.addWidget(apply_btn)
        btn_layout.addWidget(undo_btn)
        right_layout.addLayout(btn_layout)
        split.addWidget(right)

        split.setSizes([320, 680])
        layout.addWidget(split, 1)

    def _browse(self):
        path = QFileDialog.getExistingDirectory(self, "Select folder")
        if path:
            self.dir_edit.setText(path)

    def _on_dir_changed(self):
        path = self.dir_edit.text().strip()
        if not path or not os.path.isdir(path):
            self._base_dir = ""
            self._file_names = []
        else:
            self._base_dir = path
            self._file_names = sorted(
                f for f in os.listdir(path)
                if os.path.isfile(os.path.join(path, f)) and not f.startswith(".")
            )
        self._refresh_preview()

    def _on_rule_changed(self):
        """Refresh preview only if the changed rule has 'Use this rule' checked (no preview from unchecked rules)."""
        sender = self.sender()
        if sender is None:
            self._refresh_preview()
            return
        if sender in self._rule_widgets and sender.enabled_cb.isChecked():
            self._refresh_preview()
        # else: change was in an unchecked rule (e.g. mouse wheel on spinbox) — do not refresh

    def _get_rules(self) -> list[Rule]:
        """All rules in fixed order; only enabled rules are applied."""
        return [w.getRule() for w in self._rule_widgets]

    def _ext_for(self, filename: str) -> str:
        """Return file extension with leading dot, or empty string if none."""
        if "." in filename and not filename.startswith("."):
            return "." + filename.rsplit(".", 1)[-1].lower()
        return ""

    def _on_preview_selection_changed(self):
        """Show preview (new name) only in selected rows; others show original."""
        if not self._preview_by_orig:
            return
        selected_rows = {idx.row() for idx in self.preview_table.selectionModel().selectedRows()}
        for row in range(self.preview_table.rowCount()):
            orig_item = self.preview_table.item(row, 0)
            if not orig_item:
                continue
            orig = orig_item.text()
            display_name = self._preview_by_orig.get(orig, orig) if row in selected_rows else orig
            self.preview_table.setItem(row, 1, QTableWidgetItem(display_name))

    def _refresh_preview(self):
        rules = self._get_rules()
        if not self._file_names:
            self._preview_by_orig = {}
            self.preview_table.setSortingEnabled(False)
            self.preview_table.setRowCount(0)
            self.preview_status.setText("Add a folder to see files.")
            return
        # Preserve sort and selection so checking a rule doesn’t reorder or clear selection
        header = self.preview_table.horizontalHeader()
        sort_section = header.sortIndicatorSection()
        sort_order = header.sortIndicatorOrder()
        selected_orig = set()
        for idx in self.preview_table.selectionModel().selectedRows():
            item = self.preview_table.item(idx.row(), 0)
            if item:
                selected_orig.add(item.text())

        self.preview_table.setSortingEnabled(False)
        self.preview_table.setRowCount(len(self._file_names))
        for row, orig in enumerate(self._file_names):
            self.preview_table.setItem(row, 0, QTableWidgetItem(orig))
            self.preview_table.setItem(row, 1, QTableWidgetItem(orig))  # show original until selected
            self.preview_table.setItem(row, 2, QTableWidgetItem(self._ext_for(orig)))
        self.preview_table.setSortingEnabled(True)
        # Actually sort the table (setSortIndicator alone doesn’t); default to File type so episode renumber doesn’t skip
        if sort_section < 0:
            sort_section = 2
            sort_order = Qt.SortOrder.AscendingOrder
        self.preview_table.sortByColumn(sort_section, sort_order)

        # Numbering uses the table’s current (sorted) order, not load order
        order = [self.preview_table.item(r, 0).text() for r in range(self.preview_table.rowCount())]
        preview = compute_preview(order, rules)
        self._preview_by_orig = {orig: new_name for orig, new_name in preview}
        new_names = [p[1] for p in preview]
        has_collision = len(new_names) != len(set(new_names))

        # Restore selection by original name (multi-row)
        sm = self.preview_table.selectionModel()
        sm.clear()
        for row in range(self.preview_table.rowCount()):
            item = self.preview_table.item(row, 0)
            if item and item.text() in selected_orig:
                idx = self.preview_table.model().index(row, 0)
                sm.select(idx, QItemSelectionModel.SelectionFlag.Select | QItemSelectionModel.SelectionFlag.Rows)
        self._on_preview_selection_changed()
        if has_collision:
            self.preview_status.setText("Warning: some new names are duplicated. Fix rules to avoid overwriting.")
            self.preview_status.setStyleSheet("color: #c00;")
        else:
            self.preview_status.setText(f"{len(preview)} file(s). Ready to rename.")
            self.preview_status.setStyleSheet("")

    def _apply_renames(self):
        if not self._base_dir or not self._file_names:
            QMessageBox.warning(self, "No files", "Select a folder with files first.")
            return
        rules = self._get_rules()
        # Use current table order (after user sort) so numbering/episode renumber follow visible order
        if self.preview_table.rowCount() > 0:
            order = [self.preview_table.item(r, 0).text() for r in range(self.preview_table.rowCount())]
        else:
            order = self._file_names
        full_preview = compute_preview(order, rules)
        # Only rename selected rows; if nothing selected, rename all that would change
        selected_orig = {
            self.preview_table.item(idx.row(), 0).text()
            for idx in self.preview_table.selectionModel().selectedRows()
            if self.preview_table.item(idx.row(), 0)
        }
        if selected_orig:
            renames = [(a, b) for a, b in full_preview if a in selected_orig and a != b]
        else:
            renames = [(a, b) for a, b in full_preview if a != b]
        if not renames:
            QMessageBox.information(self, "Nothing to do", "No names would change with current rules.")
            return
        new_names = [b for _, b in renames]
        if len(new_names) != len(set(new_names)):
            QMessageBox.critical(
                self,
                "Collision",
                "Some new names are duplicated. Change your rules to avoid overwriting files.",
            )
            return
        ok = QMessageBox.question(
            self,
            "Confirm",
            f"Rename {len(renames)} file(s) in\n{self._base_dir}?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if ok != QMessageBox.StandardButton.Yes:
            return
        results = perform_renames(self._base_dir, renames, dry_run=False)
        errors = [r for r in results if r[2]]
        if errors:
            msg = "\n".join(f"{r[0]}: {r[2]}" for r in errors[:10])
            if len(errors) > 10:
                msg += f"\n... and {len(errors) - 10} more."
            QMessageBox.warning(self, "Rename errors", msg)
        else:
            save_undo_log(self._base_dir, renames)
            QMessageBox.information(self, "Done", f"Renamed {len(results)} file(s).")
        self._on_dir_changed()

    def _undo_renames(self):
        if not self._base_dir:
            QMessageBox.warning(self, "No folder", "Select a folder first.")
            return
        renames = load_undo_log(self._base_dir)
        if not renames:
            QMessageBox.information(
                self,
                "No undo data",
                f"No undo data for this folder.\n\nUndo is available after you run \"Apply renames\" here.",
            )
            return
        ok = QMessageBox.question(
            self,
            "Undo last rename",
            f"Revert {len(renames)} file(s) in\n{self._base_dir}\nback to their previous names?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No,
        )
        if ok != QMessageBox.StandardButton.Yes:
            return
        results = perform_undo(self._base_dir)
        errors = [r for r in results if r[2]]
        if errors:
            msg = "\n".join(f"{r[0]}: {r[2]}" for r in errors[:10])
            if len(errors) > 10:
                msg += f"\n... and {len(errors) - 10} more."
            QMessageBox.warning(self, "Undo errors", msg)
        else:
            QMessageBox.information(self, "Undone", f"Reverted {len(results)} file(s).")
        self._on_dir_changed()
