# RFX glTF Exporter main UI. PySide6 dockable window for Maya 2026.
# Modern Studio Dark redesign. No f-strings. No unicode. ASCII only.

import os
import traceback

from maya import cmds
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

from PySide6 import QtCore, QtGui, QtWidgets

from .. import settings
from ..core.exporter import GLTFExporter, GLTFImporter
from . import theme

try:
    from rfx_ui_scale import apply_maya_ui_scale
except Exception:
    def apply_maya_ui_scale(widget, scale=None, scale_window=True):
        return 1.0


WORKSPACE_CTRL = "RFX_GLTF_Exporter_Browser"
OBJECT_NAME = "RFX_GLTF_Exporter_Browser"


# ---------- small builders ----------

def _eyebrow(text):
    lbl = QtWidgets.QLabel(text.upper())
    lbl.setObjectName("RFXEyebrow")
    return lbl


def _hline():
    f = QtWidgets.QFrame()
    f.setObjectName("RFXSep")
    f.setFrameShape(QtWidgets.QFrame.NoFrame)
    return f


def _card():
    f = QtWidgets.QFrame()
    f.setObjectName("RFXCard")
    return f


def _segmented(options, default_value):
    # options: list of (value, label). Returns (frame, QButtonGroup).
    frame = QtWidgets.QFrame()
    frame.setObjectName("RFXSegGroup")
    lay = QtWidgets.QHBoxLayout(frame)
    lay.setContentsMargins(4, 4, 4, 4)
    lay.setSpacing(4)
    group = QtWidgets.QButtonGroup(frame)
    group.setExclusive(True)
    for i, (value, label) in enumerate(options):
        b = QtWidgets.QPushButton(label)
        b.setObjectName("RFXSeg")
        b.setCheckable(True)
        b.setCursor(QtCore.Qt.PointingHandCursor)
        b.value = value
        lay.addWidget(b, 1)
        group.addButton(b, i)
        if value == default_value:
            b.setChecked(True)
    return frame, group


def _seg_value(group, fallback):
    btn = group.checkedButton()
    return getattr(btn, "value", fallback) if btn else fallback


def _seg_set(group, value):
    for btn in group.buttons():
        if getattr(btn, "value", None) == value:
            btn.setChecked(True)
            return


# ---------- export worker ----------

class _ExportThread(QtCore.QThread):
    progress = QtCore.Signal(int, str)
    finished_with_report = QtCore.Signal(dict)

    def __init__(self, options, parent=None):
        super(_ExportThread, self).__init__(parent)
        self.options = options
        self._exporter = None

    def run(self):
        try:
            def _p(pct, msg):
                self.progress.emit(int(pct), msg)
            self._exporter = GLTFExporter(self.options, progress_cb=_p)
            report = self._exporter.export()
            self.finished_with_report.emit(report)
        except Exception as exc:
            self.finished_with_report.emit({
                "success": False,
                "error": "Thread error: {0}".format(exc),
                "warnings": [traceback.format_exc()],
                "mesh_count": 0,
                "source_face_count": 0,
                "source_quad_count": 0,
                "triangle_count": 0,
                "material_count": 0,
                "texture_count": 0,
                "camera_count": 0,
                "light_count": 0,
            })

    def cancel(self):
        if self._exporter is not None:
            self._exporter.cancel()


# ---------- about dialog ----------

class AboutDialog(QtWidgets.QDialog):

    def __init__(self, parent=None):
        super(AboutDialog, self).__init__(parent)
        self.setWindowTitle("About " + settings.PLUGIN_TITLE)
        self.setObjectName("RFXAboutDialog")
        self.setFixedSize(430, 512)
        self.setStyleSheet(theme.stylesheet() + self._extra_style())
        self._build()

    def _extra_style(self):
        s = theme.COLORS
        return "\n".join([
            "QDialog#RFXAboutDialog { background: " + s["panel"] + "; }",
            "QFrame#AboutHead { border: none; background: qlineargradient("
            "x1:0, y1:0, x2:1, y2:1, stop:0 #0c2b31, stop:0.55 " + s["panel"]
            + ", stop:1 #1a1430); }",
            "QLabel#AboutEyebrow { color: " + s["text_mute"]
            + "; font-size: 11px; font-weight: 600; letter-spacing: 3px; }",
            "QLabel#AboutTitle { color: " + s["accent"]
            + "; font-size: 24px; font-weight: 700; }",
            "QLabel#AboutVer { color: " + s["accent"] + "; background: " + s["accent_bg"]
            + "; padding: 2px 7px; border-radius: 4px; font-size: 11px; font-weight: 600; }",
            "QLabel#AboutBody { color: " + s["text_dim"] + "; font-size: 12px; }",
            "QLabel#AboutKey { color: " + s["text_mute"] + "; font-size: 11px; }",
            "QLabel#AboutVal { color: " + s["text"] + "; font-size: 12px; font-weight: 500; }",
            "QLabel#AboutLink { color: " + s["accent"] + "; font-size: 12px; font-weight: 500; }",
            "QLabel#AboutCopy { color: " + s["text_mute"] + "; font-size: 10px; }",
            "QPushButton#AboutClose { background: " + s["accent_bg"] + "; color: " + s["accent"]
            + "; border: 1px solid " + s["accent"]
            + "; border-radius: 8px; padding: 8px 22px; font-size: 12px; font-weight: 600; }",
            "QPushButton#AboutClose:hover { background: " + s["accent"] + "; color: #09090b; }",
        ])

    def _build(self):
        root = QtWidgets.QVBoxLayout(self)
        root.setContentsMargins(0, 0, 0, 0)
        root.setSpacing(0)

        # Gradient header
        head = QtWidgets.QFrame()
        head.setObjectName("AboutHead")
        head.setFixedHeight(132)
        hv = QtWidgets.QVBoxLayout(head)
        hv.setContentsMargins(26, 22, 26, 22)
        hv.setSpacing(8)
        eyebrow = QtWidgets.QLabel("REIMAGINE FX")
        eyebrow.setObjectName("AboutEyebrow")
        title_row = QtWidgets.QHBoxLayout()
        title_row.setSpacing(10)
        title = QtWidgets.QLabel(settings.PLUGIN_TITLE)
        title.setObjectName("AboutTitle")
        ver = QtWidgets.QLabel("v" + settings.PLUGIN_VERSION)
        ver.setObjectName("AboutVer")
        title_row.addWidget(title)
        title_row.addWidget(ver, 0, QtCore.Qt.AlignBottom)
        title_row.addStretch(1)
        hv.addWidget(eyebrow)
        hv.addStretch(1)
        hv.addLayout(title_row)
        root.addWidget(head)

        # Body
        body = QtWidgets.QWidget()
        bv = QtWidgets.QVBoxLayout(body)
        bv.setContentsMargins(26, 20, 26, 20)
        bv.setSpacing(16)

        desc = QtWidgets.QLabel(settings.ABOUT_TEXT)
        desc.setObjectName("AboutBody")
        desc.setWordWrap(True)
        bv.addWidget(desc)

        grid = QtWidgets.QGridLayout()
        grid.setHorizontalSpacing(16)
        grid.setVerticalSpacing(10)
        grid.setColumnStretch(1, 1)
        fields = [
            ("Author", settings.AUTHOR, False),
            ("Studio", settings.STUDIO, False),
            ("Website", settings.WEBSITE, True),
            ("Email", settings.EMAIL, True),
            ("Support", settings.SUPPORT, False),
        ]
        for r, (key, val, link) in enumerate(fields):
            k = QtWidgets.QLabel(key)
            k.setObjectName("AboutKey")
            v = QtWidgets.QLabel(val)
            v.setObjectName("AboutLink" if link else "AboutVal")
            grid.addWidget(k, r, 0)
            grid.addWidget(v, r, 1)
        bv.addLayout(grid)
        bv.addStretch(1)
        root.addWidget(body, 1)

        # Footer
        foot = QtWidgets.QFrame()
        fv = QtWidgets.QHBoxLayout(foot)
        fv.setContentsMargins(26, 14, 26, 18)
        copy = QtWidgets.QLabel(settings.COPYRIGHT)
        copy.setObjectName("AboutCopy")
        close = QtWidgets.QPushButton("Close")
        close.setObjectName("AboutClose")
        close.setCursor(QtCore.Qt.PointingHandCursor)
        close.clicked.connect(self.accept)
        fv.addWidget(copy)
        fv.addStretch(1)
        fv.addWidget(close)
        root.addWidget(foot)


# ---------- main window ----------

class GLTFBrowser(MayaQWidgetDockableMixin, QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(GLTFBrowser, self).__init__(parent=parent)
        self.setObjectName(OBJECT_NAME)
        self.setWindowTitle(settings.PLUGIN_LABEL)
        self.setMinimumSize(408, 560)
        self.resize(452, 752)
        self.setStyleSheet(theme.stylesheet())

        self._thread = None
        self._completed = False
        self._operation = "export"
        self._export_path = ""
        self._import_path = ""
        self._build_ui()
        self._restore_prefs()
        self._update_mode_state()
        self._update_format_state()
        apply_maya_ui_scale(self, scale_window=False)

    # ---------- build ----------

    def _build_ui(self):
        outer = QtWidgets.QVBoxLayout(self)
        outer.setContentsMargins(0, 0, 0, 0)
        outer.setSpacing(0)

        panel = QtWidgets.QFrame()
        panel.setObjectName("RFXPanel")
        outer.addWidget(panel)

        v = QtWidgets.QVBoxLayout(panel)
        v.setContentsMargins(0, 0, 0, 0)
        v.setSpacing(0)
        v.addWidget(self._build_header())
        v.addWidget(_hline())
        v.addWidget(self._build_body(), 1)
        v.addWidget(_hline())
        v.addWidget(self._build_footer())

    def _build_header(self):
        h = QtWidgets.QFrame()
        h.setObjectName("RFXHeader")
        h.setFixedHeight(62)
        lay = QtWidgets.QHBoxLayout(h)
        lay.setContentsMargins(16, 0, 16, 0)
        lay.setSpacing(11)

        # Logo sized to match the two-line title + subtitle block height.
        mark = QtWidgets.QLabel()
        mark.setFixedSize(34, 34)
        mark.setAlignment(QtCore.Qt.AlignCenter)
        pixmap = QtGui.QPixmap(settings.icon_path())
        if not pixmap.isNull():
            mark.setPixmap(pixmap.scaled(
                34, 34, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
        else:
            mark.setStyleSheet(
                "background: " + theme.color("accent") + "; border-radius: 7px;")

        text_col = QtWidgets.QVBoxLayout()
        text_col.setContentsMargins(0, 0, 0, 0)
        text_col.setSpacing(2)
        brand = QtWidgets.QLabel(settings.PLUGIN_LABEL)
        brand.setObjectName("RFXBrand")
        sub = QtWidgets.QLabel("REIMAGINE FX")
        sub.setObjectName("RFXBrandSub")
        text_col.addWidget(brand)
        text_col.addWidget(sub)

        about = QtWidgets.QPushButton("About")
        about.setObjectName("RFXAbout")
        about.setCursor(QtCore.Qt.PointingHandCursor)
        about.clicked.connect(self._on_about)

        lay.addWidget(mark, 0, QtCore.Qt.AlignVCenter)
        lay.addLayout(text_col)
        lay.setAlignment(text_col, QtCore.Qt.AlignVCenter)
        lay.addStretch(1)
        lay.addWidget(about, 0, QtCore.Qt.AlignVCenter)
        return h

    def _build_body(self):
        scroll = QtWidgets.QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setFrameShape(QtWidgets.QFrame.NoFrame)

        body = QtWidgets.QWidget()
        b = QtWidgets.QVBoxLayout(body)
        b.setContentsMargins(14, 14, 14, 14)
        b.setSpacing(13)

        mode_col = QtWidgets.QVBoxLayout()
        mode_col.setSpacing(6)
        mode_col.addWidget(_eyebrow("Mode"))
        seg_mode, self.grp_operation = _segmented(
            [("import", "Import"), ("export", "Export")], "export"
        )
        self.grp_operation.buttonClicked.connect(self._on_mode_changed)
        mode_col.addWidget(seg_mode)
        b.addLayout(mode_col)

        # Row: source + format segmented controls
        self.export_setup = QtWidgets.QWidget()
        row = QtWidgets.QHBoxLayout()
        row.setContentsMargins(0, 0, 0, 0)
        row.setSpacing(12)

        col1 = QtWidgets.QVBoxLayout()
        col1.setSpacing(6)
        col1.addWidget(_eyebrow("Export Source"))
        seg_src, self.grp_source = _segmented(
            [("selected", "Selection"), ("visible", "Scene")], "selected"
        )
        col1.addWidget(seg_src)

        col2 = QtWidgets.QVBoxLayout()
        col2.setSpacing(6)
        col2.addWidget(_eyebrow("Format"))
        seg_fmt, self.grp_format = _segmented(
            [("gltf", "glTF"), ("glb", "GLB")], "glb"
        )
        self.grp_format.buttonClicked.connect(lambda *_: self._update_format_state())
        col2.addWidget(seg_fmt)

        row.addLayout(col1, 1)
        row.addLayout(col2, 1)
        self.export_setup.setLayout(row)
        b.addWidget(self.export_setup)

        # Output path
        out_col = QtWidgets.QVBoxLayout()
        out_col.setSpacing(6)
        self.path_eyebrow = _eyebrow("Output Path")
        out_col.addWidget(self.path_eyebrow)
        b.addLayout(out_col)
        path_row = QtWidgets.QHBoxLayout()
        path_row.setSpacing(8)
        self.path_field = QtWidgets.QLineEdit()
        self.path_field.setPlaceholderText("C:/path/to/asset.glb")
        browse = QtWidgets.QPushButton("Browse")
        browse.setObjectName("RFXGhost")
        browse.setCursor(QtCore.Qt.PointingHandCursor)
        browse.clicked.connect(self._on_browse)
        path_row.addWidget(self.path_field, 1)
        path_row.addWidget(browse)
        out_col.addLayout(path_row)

        # Geometry card
        self.geometry_card = self._build_geometry_card()
        b.addWidget(self.geometry_card)

        # Materials + textures card
        self.textures_card = self._build_textures_card()
        b.addWidget(self.textures_card)

        self.import_card = self._build_import_card()
        b.addWidget(self.import_card)

        # Report / process log
        rep_col = QtWidgets.QVBoxLayout()
        rep_col.setSpacing(6)
        log_head = QtWidgets.QHBoxLayout()
        log_head.addWidget(_eyebrow("Report"))
        log_head.addStretch(1)
        self.lbl_counts = QtWidgets.QLabel("")
        self.lbl_counts.setObjectName("RFXMeta")
        log_head.addWidget(self.lbl_counts)
        rep_col.addLayout(log_head)

        self.report = QtWidgets.QPlainTextEdit()
        self.report.setReadOnly(True)
        self.report.setMinimumHeight(92)
        self.report.setPlainText("Ready.")
        rep_col.addWidget(self.report, 1)
        b.addLayout(rep_col, 1)

        scroll.setWidget(body)
        return scroll

    def _build_geometry_card(self):
        card = _card()
        v = QtWidgets.QVBoxLayout(card)
        v.setContentsMargins(14, 12, 14, 12)
        v.setSpacing(10)
        v.addWidget(_eyebrow("Geometry"))

        self.chk_normals = QtWidgets.QCheckBox("Normals")
        self.chk_tangents = QtWidgets.QCheckBox("Tangents")
        self.chk_uvs = QtWidgets.QCheckBox("UVs")
        self.chk_colors = QtWidgets.QCheckBox("Vertex colors")
        self.chk_split = QtWidgets.QCheckBox("Split by material")
        self.chk_hier = QtWidgets.QCheckBox("Preserve hierarchy")
        self.chk_flip = QtWidgets.QCheckBox("Flip UV vertical")
        self.chk_lights = QtWidgets.QCheckBox("Export lights")
        self.chk_cameras = QtWidgets.QCheckBox("Export cameras")

        grid = QtWidgets.QGridLayout()
        grid.setHorizontalSpacing(10)
        grid.setVerticalSpacing(10)
        cells = [
            (self.chk_normals, 0, 0), (self.chk_tangents, 0, 1),
            (self.chk_uvs, 1, 0), (self.chk_colors, 1, 1),
            (self.chk_split, 2, 0), (self.chk_hier, 2, 1),
            (self.chk_flip, 3, 0), (self.chk_lights, 3, 1),
            (self.chk_cameras, 4, 0),
        ]
        for chk, r, col in cells:
            chk.setCursor(QtCore.Qt.PointingHandCursor)
            grid.addWidget(chk, r, col)
        grid.setColumnStretch(0, 1)
        grid.setColumnStretch(1, 1)
        v.addLayout(grid)
        return card

    def _build_import_card(self):
        card = _card()
        v = QtWidgets.QVBoxLayout(card)
        v.setContentsMargins(14, 12, 14, 12)
        v.setSpacing(10)

        head = QtWidgets.QHBoxLayout()
        head.addWidget(_eyebrow("Scene Reconstruction"))
        head.addStretch(1)
        pill = QtWidgets.QLabel("glTF 2.0")
        pill.setObjectName("RFXPill")
        head.addWidget(pill)
        v.addLayout(head)

        self.chk_import_materials = QtWidgets.QCheckBox("Import PBR materials")
        self.chk_import_textures = QtWidgets.QCheckBox("Import textures")
        self.chk_import_cameras = QtWidgets.QCheckBox("Import cameras")
        self.chk_import_lights = QtWidgets.QCheckBox("Import punctual lights")
        self.chk_import_flip = QtWidgets.QCheckBox("Restore Maya UV vertical")
        grid = QtWidgets.QGridLayout()
        grid.setHorizontalSpacing(10)
        grid.setVerticalSpacing(10)
        cells = [
            (self.chk_import_materials, 0, 0),
            (self.chk_import_textures, 0, 1),
            (self.chk_import_cameras, 1, 0),
            (self.chk_import_lights, 1, 1),
            (self.chk_import_flip, 2, 0),
        ]
        for checkbox, row, column in cells:
            checkbox.setCursor(QtCore.Qt.PointingHandCursor)
            grid.addWidget(checkbox, row, column)
        grid.setColumnStretch(0, 1)
        grid.setColumnStretch(1, 1)
        v.addLayout(grid)

        note = QtWidgets.QLabel(
            "Embedded GLB images are extracted beside the source file so the "
            "Maya scene keeps valid texture paths."
        )
        note.setWordWrap(True)
        note.setStyleSheet("color: " + theme.color("text_mute") + "; font-size: 10px;")
        v.addWidget(note)
        return card

    def _build_textures_card(self):
        card = _card()
        v = QtWidgets.QVBoxLayout(card)
        v.setContentsMargins(14, 12, 14, 12)
        v.setSpacing(10)

        head = QtWidgets.QHBoxLayout()
        head.addWidget(_eyebrow("Materials and Textures"))
        head.addStretch(1)
        pill = QtWidgets.QLabel("Auto PBR")
        pill.setObjectName("RFXPill")
        head.addWidget(pill)
        v.addLayout(head)

        self.chk_copy = QtWidgets.QCheckBox("Copy textures into folder")
        self.chk_embed = QtWidgets.QCheckBox("Embed textures (GLB)")
        self.chk_open = QtWidgets.QCheckBox("Open folder after export")
        for c in (self.chk_copy, self.chk_embed, self.chk_open):
            c.setCursor(QtCore.Qt.PointingHandCursor)
            v.addWidget(c)

        v.addWidget(_hline())

        size_row = QtWidgets.QHBoxLayout()
        size_lbl = QtWidgets.QLabel("Max texture size")
        size_lbl.setStyleSheet("color: " + theme.color("text_dim") + "; font-size: 12px;")
        self.cmb_maxtex = QtWidgets.QComboBox()
        for label, value in (("512 px", 512), ("1024 px", 1024),
                             ("2048 px", 2048), ("4096 px", 4096),
                             ("No limit", 0)):
            self.cmb_maxtex.addItem(label, value)
        self.cmb_maxtex.setFixedWidth(120)
        size_row.addWidget(size_lbl)
        size_row.addStretch(1)
        size_row.addWidget(self.cmb_maxtex)
        v.addLayout(size_row)
        return card

    def _build_footer(self):
        f = QtWidgets.QFrame()
        f.setObjectName("RFXFooter")
        lay = QtWidgets.QVBoxLayout(f)
        lay.setContentsMargins(16, 14, 16, 16)
        lay.setSpacing(11)

        labels = QtWidgets.QHBoxLayout()
        a = QtWidgets.QLabel("PROGRESS")
        a.setObjectName("RFXMeta")
        a.setStyleSheet("color: " + theme.color("text_mute")
                        + "; font-size: 10px; letter-spacing: 1px;")
        self.lbl_status = QtWidgets.QLabel("IDLE")
        self.lbl_status.setStyleSheet("color: " + theme.color("text_mute")
                                      + "; font-size: 10px; letter-spacing: 1px;")
        labels.addWidget(a)
        labels.addStretch(1)
        labels.addWidget(self.lbl_status)
        lay.addLayout(labels)

        self.bar = QtWidgets.QProgressBar()
        self.bar.setRange(0, 100)
        self.bar.setValue(0)
        self.bar.setTextVisible(False)
        lay.addWidget(self.bar)

        btn_row = QtWidgets.QHBoxLayout()
        btn_row.setSpacing(8)
        self.btn_export = QtWidgets.QPushButton("Export")
        self.btn_export.setObjectName("RFXExport")
        self.btn_export.setCursor(QtCore.Qt.PointingHandCursor)
        self.btn_export.clicked.connect(self._on_primary_action)
        self.btn_cancel = QtWidgets.QPushButton("Cancel")
        self.btn_cancel.setObjectName("RFXCancel")
        self.btn_cancel.setCursor(QtCore.Qt.PointingHandCursor)
        self.btn_cancel.setEnabled(False)
        self.btn_cancel.clicked.connect(self._on_secondary_action)
        btn_row.addWidget(self.btn_export, 2)
        btn_row.addWidget(self.btn_cancel, 1)
        lay.addLayout(btn_row)
        return f

    # ---------- callbacks ----------

    def _on_mode_changed(self, *_args):
        next_operation = _seg_value(self.grp_operation, "export")
        if self._operation == "export":
            self._export_path = self.path_field.text()
        else:
            self._import_path = self.path_field.text()
        self._operation = next_operation
        self.path_field.setText(
            self._import_path if self._operation == "import" else self._export_path
        )
        self._completed = False
        self._update_mode_state()
        self._set_idle_actions()
        self.report.setPlainText("Ready.")
        self.lbl_counts.setText("")
        self.bar.setValue(0)
        self.lbl_status.setText("IDLE")

    def _update_mode_state(self):
        importing = self._operation == "import"
        self.export_setup.setVisible(not importing)
        self.geometry_card.setVisible(not importing)
        self.textures_card.setVisible(not importing)
        self.import_card.setVisible(importing)
        self.path_eyebrow.setText("INPUT FILE" if importing else "OUTPUT PATH")
        self.path_field.setPlaceholderText(
            "C:/path/to/asset.glb" if importing else "C:/path/to/export.glb"
        )

    def _update_format_state(self):
        if self._operation != "export":
            return
        fmt = _seg_value(self.grp_format, "glb")
        cur = self.path_field.text() or ""
        if cur:
            stem, _ext = os.path.splitext(cur)
            self.path_field.setText(stem + (".glb" if fmt == "glb" else ".gltf"))
        if fmt == "glb":
            self.chk_embed.setEnabled(True)
        else:
            self.chk_embed.setChecked(False)
            self.chk_embed.setEnabled(False)

    def _on_about(self):
        dlg = AboutDialog(self)
        dlg.exec()

    def _on_browse(self):
        current = self.path_field.text() or ""
        if current:
            start = os.path.dirname(current)
        else:
            start = cmds.workspace(query=True, directory=True) or os.path.expanduser("~")
        if self._operation == "import":
            result, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, "Choose glTF File", start,
                "glTF 2.0 (*.glb *.gltf);;GLB (*.glb);;glTF (*.gltf)"
            )
            if result:
                self.path_field.setText(result)
                self._import_path = result
            return
        fmt = _seg_value(self.grp_format, "glb")
        flt = "glTF Binary (*.glb)" if fmt == "glb" else "glTF 2.0 (*.gltf)"
        result, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Choose Output", start, flt)
        if not result:
            return
        ext = ".glb" if fmt == "glb" else ".gltf"
        if not result.lower().endswith(ext):
            result = result + ext
        self.path_field.setText(result)
        self._export_path = result

    def _gather_options(self):
        return {
            "output_path": (self.path_field.text() or "").strip(),
            "format": _seg_value(self.grp_format, "glb"),
            "export_mode": _seg_value(self.grp_source, "selected"),
            "copy_textures": self.chk_copy.isChecked(),
            "embed_textures": self.chk_embed.isChecked(),
            "max_texture_size": int(self.cmb_maxtex.currentData() or 0),
            "include_normals": self.chk_normals.isChecked(),
            "include_tangents": self.chk_tangents.isChecked(),
            "include_uvs": self.chk_uvs.isChecked(),
            "flip_uv_v": self.chk_flip.isChecked(),
            "include_vertex_colors": self.chk_colors.isChecked(),
            "include_hierarchy": self.chk_hier.isChecked(),
            "include_lights": self.chk_lights.isChecked(),
            "include_cameras": self.chk_cameras.isChecked(),
            "split_by_material": self.chk_split.isChecked(),
            "open_folder": self.chk_open.isChecked(),
        }

    def _gather_import_options(self):
        return {
            "input_path": (self.path_field.text() or "").strip(),
            "import_materials": self.chk_import_materials.isChecked(),
            "import_textures": self.chk_import_textures.isChecked(),
            "import_cameras": self.chk_import_cameras.isChecked(),
            "import_lights": self.chk_import_lights.isChecked(),
            "import_flip_uv_v": self.chk_import_flip.isChecked(),
        }

    def _on_primary_action(self):
        if self._operation == "import":
            self._on_import()
        else:
            self._on_export()

    def _on_export(self):
        options = self._gather_options()
        if not options["output_path"]:
            QtWidgets.QMessageBox.warning(self, settings.PLUGIN_LABEL,
                                          "Please choose an output path first.")
            return
        self._export_path = options["output_path"]
        options["operation"] = "export"
        options["input_path"] = self._import_path
        self._save_prefs(options)
        self._set_running_actions()
        self.lbl_counts.setText("")
        self.report.setPlainText("[run] starting export...")
        self.bar.setValue(0)
        self.lbl_status.setText("0%")

        self._thread = _ExportThread(options, parent=self)
        self._thread.progress.connect(self._on_progress)
        self._thread.finished_with_report.connect(self._on_finished)
        self._thread.start()

    def _on_import(self):
        options = self._gather_import_options()
        if not options["input_path"]:
            QtWidgets.QMessageBox.warning(self, settings.PLUGIN_LABEL,
                                          "Please choose a glTF or GLB file first.")
            return
        self._import_path = options["input_path"]
        prefs = settings.load_prefs() or {}
        prefs.update(options)
        prefs["output_path"] = self._export_path
        prefs["operation"] = "import"
        self._save_prefs(prefs)
        self._set_running_actions()
        self.btn_cancel.setEnabled(False)
        self.lbl_counts.setText("")
        self.report.setPlainText("[run] starting import...")
        self.bar.setValue(0)
        self.lbl_status.setText("0%")
        QtWidgets.QApplication.processEvents()
        importer = GLTFImporter(options, progress_cb=self._on_progress)
        report = importer.import_file()
        report["operation"] = "import"
        self._on_finished(report)

    def _on_secondary_action(self):
        if self._completed:
            self.close()
            return
        self._on_cancel()

    def _on_cancel(self):
        if self._thread is not None:
            self._thread.cancel()
            self.btn_cancel.setEnabled(False)
            self.lbl_status.setText("CANCELLING")

    def _on_progress(self, pct, msg):
        self.bar.setValue(pct)
        self.lbl_status.setText("{0}%".format(pct))
        if msg:
            self.report.appendPlainText("[{0:>3}%] {1}".format(pct, msg))
        QtWidgets.QApplication.processEvents()

    def _on_finished(self, report):
        operation = report.get("operation", "export")
        text = self._format_report(report)
        self.report.setPlainText(text)
        print("[{0}] {1}".format(settings.PLUGIN_LABEL, text))

        self.lbl_counts.setText("{0} mesh / {1} tri / {2} mat".format(
            report.get("mesh_count", 0), report.get("triangle_count", 0),
            report.get("material_count", 0)))

        if report.get("success"):
            self.bar.setValue(100)
            self.lbl_status.setText("DONE")
            self._set_success_actions()
        else:
            self.lbl_status.setText("FAILED")
            self._set_idle_actions()
            QtWidgets.QMessageBox.warning(self, settings.PLUGIN_LABEL,
                                          report.get("error") or
                                          "{0} failed. See the report.".format(
                                              operation.title()))

    def _refresh_button_style(self, button):
        style = button.style()
        style.unpolish(button)
        style.polish(button)
        button.update()

    def _set_idle_actions(self):
        self._completed = False
        self.btn_export.setText("Import" if self._operation == "import" else "Export")
        self.btn_export.setEnabled(True)
        self.btn_cancel.setObjectName("RFXCancel")
        self.btn_cancel.setText("Cancel")
        self.btn_cancel.setEnabled(False)
        self._refresh_button_style(self.btn_cancel)

    def _set_running_actions(self):
        self._completed = False
        self.btn_export.setText(
            "Importing..." if self._operation == "import" else "Exporting..."
        )
        self.btn_export.setEnabled(False)
        self.btn_cancel.setObjectName("RFXCancel")
        self.btn_cancel.setText("Cancel")
        self.btn_cancel.setEnabled(True)
        self._refresh_button_style(self.btn_cancel)

    def _set_success_actions(self):
        self._completed = True
        self.btn_export.setText(
            "Import Again" if self._operation == "import" else "Export Again"
        )
        self.btn_export.setEnabled(True)
        self.btn_cancel.setObjectName("RFXDone")
        self.btn_cancel.setText("Done")
        self.btn_cancel.setEnabled(True)
        self._refresh_button_style(self.btn_cancel)

    def _format_report(self, report):
        lines = []
        operation = report.get("operation", "export")
        label = "Import" if operation == "import" else "Export"
        lines.append(label + (" complete." if report.get("success") else " failed."))
        if report.get("input_path"):
            lines.append("File:       " + str(report["input_path"]))
        if report.get("output_path"):
            lines.append("File:       " + str(report["output_path"]))
        if report.get("bin_path"):
            lines.append("Buffer:     " + str(report["bin_path"]))
        lines.append("Format:     " + str(report.get("format")))
        lines.append("Meshes:     " + str(report.get("mesh_count", 0)))
        lines.append("Primitives: " + str(report.get("primitive_count", 0)))
        if operation == "export":
            lines.append("Source faces: {0} ({1} quads, {2} n-gons)".format(
                report.get("source_face_count", 0),
                report.get("source_quad_count", 0),
                report.get("source_ngon_count", 0),
            ))
        lines.append("Triangles:  " + str(report.get("triangle_count", 0)))
        lines.append("Materials:  " + str(report.get("material_count", 0)))
        lines.append("Textures:   " + str(report.get("texture_count", 0)))
        lines.append("Cameras:    " + str(report.get("camera_count", 0)))
        lines.append("Lights:     " + str(report.get("light_count", 0)))
        if report.get("root"):
            lines.append("Root:       " + str(report.get("root")))
        if report.get("unsupported_light_count", 0):
            lines.append("Skipped lights: " + str(
                report.get("unsupported_light_count", 0)
            ))
        if report.get("error"):
            lines.append("")
            lines.append("Error: " + str(report["error"]))
        warnings = report.get("warnings") or []
        if warnings:
            lines.append("")
            lines.append("Warnings (" + str(len(warnings)) + "):")
            for w in warnings:
                lines.append("  - " + str(w))
        return "\n".join(lines)

    # ---------- prefs ----------

    def _save_prefs(self, options):
        try:
            settings.save_prefs(options)
        except Exception:
            pass

    def _restore_prefs(self):
        prefs = settings.load_prefs() or {}
        opts = dict(settings.DEFAULT_OPTIONS)
        opts.update(prefs)
        self._export_path = opts.get("output_path", "")
        self._import_path = opts.get("input_path", "")
        self._operation = opts.get("operation", "export")
        _seg_set(self.grp_operation, self._operation)
        self.path_field.setText(
            self._import_path if self._operation == "import" else self._export_path
        )
        _seg_set(self.grp_format, opts.get("format", "glb"))
        _seg_set(self.grp_source, opts.get("export_mode", "selected"))
        self.chk_normals.setChecked(opts.get("include_normals", True))
        self.chk_tangents.setChecked(opts.get("include_tangents", True))
        self.chk_uvs.setChecked(opts.get("include_uvs", True))
        self.chk_colors.setChecked(opts.get("include_vertex_colors", False))
        self.chk_split.setChecked(opts.get("split_by_material", True))
        self.chk_hier.setChecked(opts.get("include_hierarchy", True))
        self.chk_flip.setChecked(opts.get("flip_uv_v", True))
        self.chk_lights.setChecked(opts.get("include_lights", True))
        self.chk_cameras.setChecked(opts.get("include_cameras", True))
        self.chk_copy.setChecked(opts.get("copy_textures", True))
        self.chk_embed.setChecked(opts.get("embed_textures", False))
        self.chk_open.setChecked(opts.get("open_folder", False))
        self.chk_import_materials.setChecked(opts.get("import_materials", True))
        self.chk_import_textures.setChecked(opts.get("import_textures", True))
        self.chk_import_cameras.setChecked(opts.get("import_cameras", True))
        self.chk_import_lights.setChecked(opts.get("import_lights", True))
        self.chk_import_flip.setChecked(opts.get("import_flip_uv_v", True))
        target = int(opts.get("max_texture_size", 2048) or 0)
        idx = self.cmb_maxtex.findData(target)
        if idx >= 0:
            self.cmb_maxtex.setCurrentIndex(idx)


def _cleanup_existing():
    try:
        ws = WORKSPACE_CTRL + "WorkspaceControl"
        if cmds.workspaceControl(ws, exists=True):
            cmds.deleteUI(ws, control=True)
    except Exception:
        pass


def show():
    _cleanup_existing()
    win = GLTFBrowser()
    win.show(dockable=True, floating=False, area="right", allowedArea="all")
    try:
        from rfx_ui_scale import attach_screen_watcher
        attach_screen_watcher(win, scale_window=True)
    except Exception:
        pass
    return win
