from bcad.binterpreter.rqq import (
    rqQueue, requests, replies,
    RQ_LOAD_IMAGE, RQ_START_ROTATION, RQ_ROTATE, RQ_SET_SIZE, RQ_STOP,
    RQ_SCROLL, RQ_MOVE, RQ_PAN, RQ_CHECK_REDRAW, RQ_GET_OBJECT_TREE, RQ_SET_VIEW,
    RQ_RELOAD_FROM_DATA, RQ_SET_MEASURE_MODE, RQ_SELECT, RQ_MEASURE_CLEAR,
    RQ_SET_DISPLAY_MODE, RQ_SET_PROJECTION, RQ_VIEW_ACTION, RQ_END_INTERACTION, RQ_ZOOM, RQ_START_ZOOM,
    RQ_EXPORT_GEOMETRY, RQ_CANCEL,
    RP_IMAGE_DATA, RP_ACK, RP_ACK_SET_SIZE, RP_ACK_GET_OBJECT_TREE, RP_NOP,
    RP_SETTING, RP_LOG, RP_MEASURE_RESULT, RP_PROGRESS,
)

from bcad.binterpreter.offscreen_display import offscreenViewer3d
from bcad.binterpreter.glfw_display import glfwViewer3d

from bcad.binterpreter.events import ep
from bcad.binterpreter.singleton import Singleton
import re

import logging
import ctypes
from imgui_bundle import imgui
from .file_dialogs import open_file, save_file
from bcad.binterpreter.bcode_editor import EditorWindow
from bcad.binterpreter.bcode_tools import ViewTools
from bcad.binterpreter.icon_loader import IconLoader
from bcad.binterpreter.log_view import LogView

import json
import os
import time


class MainWindow():
    def __init__(self, gui=True, pipe=None, img=None, filepath=None):
        self.use_imgui = gui
        self.use_occt = not gui
        self.please_stop = False
        self._exit_pending = False
        self.rqq = rqQueue()
        self.texture_updated = False
        self.prev_pos = [0, 0]
        self.offscreen_view_size = [0,0]
        self._filepath = filepath
        self.output_view = LogView()
        self.console_view = LogView()
        self._ctrl_f4_down = False
        self._ctrl_o_down = False
        self.measure_mode = False
        self._rotation_started = False
        self._was_dragging = False
        self._show_imgui_demo = False
        self._show_imgui_metrics = False
        self._show_imgui_debug_log = False
        self._show_imgui_id_stack_tool = False
        self._show_imgui_style_editor = False
        self._show_about = False
        self._mid_drag_start_y = 0
        self._reloading = False
        self._reload_progress = {}
        self._last_rendered_path = None
        self._previous_rendered_path = None

        if self.use_imgui:
            logging.info("Creating IMGUI context")
            imgui.create_context()
            imgui.style_colors_light()
        else:
            logging.info("IMGUI disabled")

        if self.use_imgui:
            self.canva = glfwViewer3d()
            self.canva.set_pipe(pipe)
            self.canva.set_img(img)
            self.canva.init_driver()

            window_address = ctypes.cast(self.canva.window, ctypes.c_void_p).value
            imgui.backends.glfw_init_for_opengl(window_address, True)
            imgui.backends.opengl3_init("#version 330")

            # Code editor
            EditorWindow(filepath)

            self.objtree = None
            self.show_objtree = False
            self.show_views = False
        else:
            self.canva = offscreenViewer3d()
            self.canva.set_pipe(pipe)
            self.canva.set_img(img)

    def parse_reply(self):
        rp = self.canva.pipe.recv()
        jdata = json.loads(rp)
        if jdata['rp'] == replies[RP_IMAGE_DATA]:
            if (self.offscreen_view_size[0] == self.canva.view_size[0]) and (self.offscreen_view_size[1] == self.canva.view_size[1]):
                self.canva.set_image(self.canva.view_size[0], self.canva.view_size[1], data=self.canva.img.buf)
            else:
                self.canva.set_image_black()
            self.canva.reply_received()
            if self._reloading:
                self._reloading = False
                self._reload_progress = {}
                if self._last_rendered_path != self._previous_rendered_path:
                    self._previous_rendered_path = self._last_rendered_path
                    self.rqq.rq_view_action('reset_view')
                    self.rqq.rq_view_action('fit_all')
        elif jdata['rp'] == replies[RP_ACK]:
            if (self.offscreen_view_size[0] == self.canva.view_size[0]) and (self.offscreen_view_size[1] == self.canva.view_size[1]):
                self.canva.set_image(self.canva.view_size[0], self.canva.view_size[1], data=self.canva.img.buf)
            else:
                self.canva.set_image_black()
            self.canva.reply_received()
            if self._reloading:
                self._reloading = False
                self._reload_progress = {}
                if self._last_rendered_path != self._previous_rendered_path:
                    self._previous_rendered_path = self._last_rendered_path
                    self.rqq.rq_view_action('reset_view')
                    self.rqq.rq_view_action('fit_all')
        elif jdata['rp'] == replies[RP_ACK_SET_SIZE]:
            self.offscreen_view_size = [jdata['args'][0], jdata['args'][1]]
            if (self.offscreen_view_size[0] == self.canva.view_size[0]) and (self.offscreen_view_size[1] == self.canva.view_size[1]):
                self.canva.set_image(self.canva.view_size[0], self.canva.view_size[1], data=self.canva.img.buf)
            self.canva.reply_received()
        elif jdata['rp'] == replies[RP_ACK_GET_OBJECT_TREE]:
            objtree = jdata['args']
            self.objtree = objtree
            self.canva.reply_received()
        elif jdata['rp'] == replies[RP_SETTING]:
            section, key, value = jdata['args']
            Singleton.settings.set(section, key, value)
            self.canva.reply_received()
        elif jdata['rp'] == replies[RP_LOG]:
            category, message = jdata['args']
            if category == 'output':
                self.output_view.add(message)
            elif category == 'console':
                self.console_view.add(message)
            self.canva.reply_received()
        elif jdata['rp'] == replies[RP_MEASURE_RESULT]:
            text = jdata['args'][0]
            self.output_view.add(text)
            self.canva.reply_received()
        elif jdata['rp'] == replies[RP_NOP]:
            self.canva.reply_received()

    def object_node(self, node):
        if node:
            if node['children']:
                if (imgui.tree_node(node['name'])):
                    for c in node['children']:
                        self.object_node(c)
                    imgui.tree_pop()
            else:
                imgui.text(node['name'])

    def object_tree(self, first_frame, x, y, w, h):
        if first_frame:
            imgui.set_next_window_pos(imgui.ImVec2(x, y), cond=imgui.Cond_.always)
        imgui.begin("Objects")

        if self.objtree:
            self.object_node(self.objtree)

        hovered = imgui.is_window_hovered()
        wh = imgui.get_window_size()

        imgui.end()
        return hovered, wh

    def views_list(self, first_frame, x, y, w, h):
        if first_frame:
            imgui.set_next_window_pos(imgui.ImVec2(x, y), cond=imgui.Cond_.always)
        imgui.begin("Views")
        if imgui.button('Left'):
            self.rqq.rq_set_view('left')
        elif imgui.button('Right'):
            self.rqq.rq_set_view('right')
        elif imgui.button('Top'):
            self.rqq.rq_set_view('top')
        elif imgui.button('Bottom'):
            self.rqq.rq_set_view('bottom')
        elif imgui.button('Front'):
            self.rqq.rq_set_view('front')
        elif imgui.button('Rear'):
            self.rqq.rq_set_view('rear')
        elif imgui.button('Iso1'):
            self.rqq.rq_set_view('iso1')
        elif imgui.button('Iso2'):
            self.rqq.rq_set_view('iso2')
        elif imgui.button('Iso3'):
            self.rqq.rq_set_view('iso3')
        elif imgui.button('Iso4'):
            self.rqq.rq_set_view('iso4')
        elif imgui.button('Iso5'):
            self.rqq.rq_set_view('iso5')
        elif imgui.button('Iso6'):
            self.rqq.rq_set_view('iso6')
        elif imgui.button('Iso7'):
            self.rqq.rq_set_view('iso7')
        elif imgui.button('Iso8'):
            self.rqq.rq_set_view('iso8')
        hovered = imgui.is_window_hovered()
        wh = imgui.get_window_size()

        imgui.end()
        return hovered, wh

    def _open_file_dialog(self):
        ed = EditorWindow._active_instance
        current = ed.filepath if ed else ""
        results = open_file("Open file", current_path=current, multiselect=True)
        if results:
            for f in results:
                EditorWindow(f)
            for inst in list(EditorWindow._instances):
                if inst.filepath is None and not inst.dirty and inst.editor.get_text() == "":
                    inst.open = False

    def _save_focused(self):
        ed = EditorWindow._active_instance
        return ed._save() if ed else False

    def _save_as_focused(self):
        ed = EditorWindow._active_instance
        return ed._save_as() if ed else False

    def _close_focused(self):
        ed = EditorWindow._active_instance
        if ed is None:
            return
        if ed.dirty:
            ed.close_prompt = True
        else:
            ed.open = False

    def _try_exit(self):
        EditorWindow._exit_in_progress = True
        any_dirty = False
        self._exit_layout_ids = EditorWindow.get_dock_ids_to_save()
        for ed in EditorWindow._instances:
            if ed.open and ed.dirty:
                ed.close_prompt = True
                any_dirty = True
            elif ed.open:
                ed.open = False
        if not any_dirty:
            self._exit_pending = False
            self.please_stop = True
        else:
            self._exit_pending = True

    def _reload_active(self):
        if self._reloading:
            return
        self.output_view.clear()
        self.console_view.clear()
        ed = EditorWindow._active_instance
        if ed is None:
            return
        self.measure_mode = False
        code = ed.editor.get_text()
        path = ed.filepath or ""
        self._last_rendered_path = path
        self._reloading = True
        self._reload_progress = {}
        self.rqq.rq_cancel()
        self.canva.send_rq(json.dumps({
            'rq': requests[RQ_RELOAD_FROM_DATA],
            'args': [code, path]
        }))

    def mainloop(self):
        if self.use_imgui:
            self.canva.create_texture()

            _loader = IconLoader()
            _loader.load()
            ViewTools.icon_loader = _loader

            self.rqq.rq_set_size(self.canva.view_size[0], self.canva.view_size[1])
            logging.info("Waiting set size reply")
            self.rqq.process(self.canva)
            self.parse_reply()

            self.rqq.rq_get_object_tree()
            logging.info("Waiting get object tree reply")
            self.rqq.process(self.canva)
            self.parse_reply()

            self.rqq.rq_load_image()
            last = time.time()
            first_frame = True

            io = imgui.get_io()
            io.config_flags |= imgui.ConfigFlags_.docking_enable
            layout_path = os.path.join(
                Singleton.settings.get_config_dir(), 'layout.ini')
            io.set_ini_filename(layout_path)
            io.ini_saving_rate = 0
            if os.path.exists(layout_path):
                with open(layout_path, 'r') as _f:
                    _content = _f.read()
                _m = re.search(r'\[EditorDockNodes\]\s*\n\s*ids\s*=\s*(.+?)(?:\n|$)', _content)
                if _m:
                    EditorWindow._saved_dock_ids = [int(x, 16) for x in _m.group(1).split(',') if x.strip()]
                imgui.load_ini_settings_from_disk(layout_path)

            # Load font with unicode support (replaces default ProggyClean)
            font_path = "/usr/share/fonts/TTF/DejaVuSansMono.ttf"
            if os.path.isfile(font_path):
                self.editor_font = io.fonts.add_font_from_file_ttf(font_path, 16.0)
                io.font_default = self.editor_font

            self._f5_down = False

            def _abgr(rgb):
                return (255 << 24) | (rgb[2] << 16) | (rgb[1] << 8) | rgb[0]

            def _update_bg_color1(section, key, value, old):
                rgb = [int(x.strip()) for x in value.split(',')]
                self.canva.bg_color = _abgr(rgb)
                self.canva.grad_dirty = True
            def _update_bg_color2(section, key, value, old):
                rgb = [int(x.strip()) for x in value.split(',')]
                self.canva.bg_color2 = _abgr(rgb)
                self.canva.grad_dirty = True
            Singleton.settings.observe('display', 'bg_gradient1', _update_bg_color1)
            Singleton.settings.observe('display', 'bg_gradient2', _update_bg_color2)
            bg1 = Singleton.settings.get_rgb('display', 'bg_gradient1')
            bg2 = Singleton.settings.get_rgb('display', 'bg_gradient2')
            self.canva.bg_color = _abgr(bg1)
            self.canva.bg_color2 = _abgr(bg2)
            self.canva.grad_dirty = True

            import glfw

            def _window_close_cb(window):
                self._try_exit()
                glfw.set_window_should_close(window, False)

            glfw.set_window_close_callback(self.canva.window, _window_close_cb)

            while (not self.canva.should_close() and (not self.please_stop)):
                current = time.time()

                w = self.canva.window
                ctrl = glfw.get_key(w, glfw.KEY_LEFT_CONTROL) == glfw.PRESS or \
                       glfw.get_key(w, glfw.KEY_RIGHT_CONTROL) == glfw.PRESS
                shift = glfw.get_key(w, glfw.KEY_LEFT_SHIFT) == glfw.PRESS or \
                        glfw.get_key(w, glfw.KEY_RIGHT_SHIFT) == glfw.PRESS
                alt = glfw.get_key(w, glfw.KEY_LEFT_ALT) == glfw.PRESS or \
                      glfw.get_key(w, glfw.KEY_RIGHT_ALT) == glfw.PRESS

                f5_state = glfw.get_key(w, glfw.KEY_F5)
                if f5_state == glfw.PRESS:
                    if not self._f5_down:
                        self._f5_down = True
                        self._reload_active()
                else:
                    self._f5_down = False

                n_state = glfw.get_key(w, glfw.KEY_N)
                if ctrl and n_state == glfw.PRESS and not self._ctrl_n_down:
                    self._ctrl_n_down = True
                    EditorWindow()
                elif n_state == glfw.RELEASE:
                    self._ctrl_n_down = False

                f4_state = glfw.get_key(w, glfw.KEY_F4)
                if ctrl and f4_state == glfw.PRESS and not self._ctrl_f4_down:
                    self._ctrl_f4_down = True
                    self._close_focused()
                elif f4_state == glfw.RELEASE:
                    self._ctrl_f4_down = False

                s_state = glfw.get_key(w, glfw.KEY_S)
                if ctrl and s_state == glfw.PRESS and not self._ctrl_s_down:
                    self._ctrl_s_down = True
                    if shift:
                        self._save_as_focused()
                    else:
                        self._save_focused()
                elif s_state == glfw.RELEASE:
                    self._ctrl_s_down = False

                o_state = glfw.get_key(w, glfw.KEY_O)
                if ctrl and o_state == glfw.PRESS and not self._ctrl_o_down:
                    self._ctrl_o_down = True
                    self._open_file_dialog()
                elif o_state == glfw.RELEASE:
                    self._ctrl_o_down = False

                x_state = glfw.get_key(w, glfw.KEY_X)
                if alt and x_state == glfw.PRESS:
                    self._try_exit()
                if self.canva.get_need_resize():
                    self.rqq.rq_set_size(self.canva.view_size[0], self.canva.view_size[1])
                    self.canva.poll_events()
                    continue

                self.rqq.process(self.canva)
                if (current-last)>1:
                    last = current
                    self.rqq.rq_check_redraw()
                if self.canva.pipe.poll():
                    self.parse_reply()

                imgui.backends.glfw_new_frame()
                imgui.backends.opengl3_new_frame()
                imgui.new_frame()

                if imgui.begin_main_menu_bar():
                    if imgui.begin_menu("File", True):
                        clicked_new, _ = imgui.menu_item("New", 'Ctrl+N', False, True)
                        if clicked_new:
                            EditorWindow()
                        clicked_open, _ = imgui.menu_item("Open...", 'Ctrl+O', False, True)
                        if clicked_open:
                            self._open_file_dialog()
                        if imgui.begin_menu("Examples", True):
                            examples_dir = os.path.join(os.path.dirname(__file__), '..', 'assets', 'examples')
                            if os.path.isdir(examples_dir):
                                for f in sorted(os.listdir(examples_dir)):
                                    if f.endswith('.scad'):
                                        if imgui.menu_item(f, "", False, True)[0]:
                                            EditorWindow(os.path.join(examples_dir, f))
                            imgui.end_menu()
                        imgui.separator()
                        clicked_save, _ = imgui.menu_item("Save", 'Ctrl+S', False, True)
                        if clicked_save:
                            self._save_focused()
                        clicked_save_as, _ = imgui.menu_item("Save As...", 'Ctrl+Shift+S', False, True)
                        if clicked_save_as:
                            self._save_as_focused()
                        imgui.separator()
                        if imgui.begin_menu("Export", True):
                            if imgui.menu_item("Image (PNG)...", "", False, True)[0]:
                                ViewTools.export_image(self.rqq)
                            imgui.separator()
                            if imgui.menu_item("STEP...", "", False, True)[0]:
                                path = save_file("Export STEP", filters=["STEP files", "*.step"])
                                if path:
                                    self.rqq.rq_export_geometry(path, 'step')
                            if imgui.menu_item("STL...", "", False, True)[0]:
                                path = save_file("Export STL", filters=["STL files", "*.stl"])
                                if path:
                                    self.rqq.rq_export_geometry(path, 'stl')
                            if imgui.menu_item("DXF...", "", False, True)[0]:
                                path = save_file("Export DXF", filters=["DXF files", "*.dxf"])
                                if path:
                                    self.rqq.rq_export_geometry(path, 'dxf')
                            imgui.end_menu()
                        imgui.separator()
                        clicked_close, _ = imgui.menu_item("Close", 'Ctrl+F4', False, True)
                        if clicked_close:
                            self._close_focused()
                        clicked_quit, _ = imgui.menu_item("Quit", 'Alt+X', False, True)
                        if clicked_quit:
                            self._try_exit()
                        imgui.end_menu()
                    if imgui.begin_menu("Render", True):
                        clicked_reload, _ = imgui.menu_item("Reload", 'F5', False, not self._reloading)
                        if clicked_reload:
                            self._reload_active()
                        imgui.end_menu()
                    if imgui.begin_menu("View", True):
                        ViewTools.render_view_menu(self.rqq)
                        imgui.end_menu()
                    if imgui.begin_menu("Tools", True):
                        clicked_measure, self.measure_mode = imgui.menu_item("Toggle Measure Mode", "", self.measure_mode, True)
                        if clicked_measure:
                            self.rqq.rq_set_measure_mode(self.measure_mode)
                        imgui.end_menu()
                    if imgui.begin_menu("Help", True):
                        if imgui.begin_menu("Development", True):
                            _, self._show_imgui_demo = imgui.menu_item("ImGui Demo", "", self._show_imgui_demo, True)
                            _, self._show_imgui_metrics = imgui.menu_item("ImGui Metrics", "", self._show_imgui_metrics, True)
                            _, self._show_imgui_debug_log = imgui.menu_item("ImGui Debug Log", "", self._show_imgui_debug_log, True)
                            _, self._show_imgui_id_stack_tool = imgui.menu_item("ImGui ID Stack Tool", "", self._show_imgui_id_stack_tool, True)
                            _, self._show_imgui_style_editor = imgui.menu_item("ImGui Style Editor", "", self._show_imgui_style_editor, True)
                            imgui.end_menu()
                        imgui.separator()
                        if imgui.menu_item("About bcad...", "", False, True)[0]:
                            self._show_about = True
                        imgui.end_menu()
                    imgui.end_main_menu_bar()

                dockspace_id = imgui.dock_space_over_viewport()

                if first_frame:
                    if not os.path.exists(layout_path):
                        imgui.internal.dock_builder_remove_node(dockspace_id)
                        imgui.internal.dock_builder_add_node(dockspace_id)
                        _, dock_left_id, dock_right = imgui.internal.dock_builder_split_node_py(dockspace_id, imgui.Dir_.left, 0.3)
                        EditorWindow._dock_left_id = dock_left_id
                        _, output_dock, viewport_dock = imgui.internal.dock_builder_split_node_py(dock_right, imgui.Dir_.down, 0.2)
                        for ed in EditorWindow._instances:
                            wid = f"{ed.title}###editor_{id(ed)}"
                            imgui.internal.dock_builder_dock_window(wid, dock_left_id)
                        imgui.internal.dock_builder_dock_window("3D Viewport", viewport_dock)
                        _, output_dock, console_dock = imgui.internal.dock_builder_split_node_py(output_dock, imgui.Dir_.left, 0.5)
                        imgui.internal.dock_builder_dock_window("Output", output_dock)
                        imgui.internal.dock_builder_dock_window("Console", console_dock)
                        imgui.internal.dock_builder_finish(dockspace_id)

                imgui.push_style_var(imgui.StyleVar_.window_padding, imgui.ImVec2(0, 0))
                imgui.begin("3D Viewport")

                avail = imgui.get_content_region_avail()
                toolbar_h = 32
                vp_w = max(1, int(avail.x))
                vp_h = max(1, int(avail.y - toolbar_h))

                if vp_w != self.canva.view_size[0] or vp_h != self.canva.view_size[1]:
                    if not io.mouse_down[0] and not io.mouse_down[1] and not io.mouse_down[2]:
                        self.canva.view_size = [vp_w, vp_h]
                        self.canva.need_resize = True

                cs = imgui.get_cursor_screen_pos()
                wx, wy = cs.x, cs.y

                if self.canva.texture:
                    tw = self.canva.tex_w
                    th = self.canva.tex_h
                    if tw > 0 and th > 0:
                        dl = imgui.get_window_draw_list()

                        crop_x = max(0, tw - vp_w) / 2.0
                        crop_y = max(0, th - vp_h) / 2.0

                        draw_w = min(vp_w, tw)
                        draw_h = min(vp_h, th)

                        u0 = crop_x / tw
                        u1 = (crop_x + draw_w) / tw
                        v0 = 1.0 - crop_y / th
                        v1 = 1.0 - (crop_y + draw_h) / th

                        offset_x = (vp_w - draw_w) * 0.5
                        offset_y = (vp_h - draw_h) * 0.5

                        c1 = self.canva.bg_color
                        c2 = self.canva.bg_color2

                        if offset_y > 0:
                            dl.add_rect_filled(
                                imgui.ImVec2(wx, wy),
                                imgui.ImVec2(wx + vp_w, wy + offset_y),
                                c1,
                            )
                            dl.add_rect_filled(
                                imgui.ImVec2(wx, wy + offset_y + draw_h),
                                imgui.ImVec2(wx + vp_w, wy + vp_h),
                                c2,
                            )

                        if offset_x > 0:
                            if self.canva.grad_dirty or self.canva.grad_tex_h != draw_h:
                                self.canva._make_gradient_tex(draw_h)
                            left_w = offset_x
                            right_w = vp_w - offset_x - draw_w
                            tex_id = self.canva.grad_tex
                            if left_w > 0 and tex_id:
                                dl.add_image(
                                    imgui.ImTextureRef(tex_id),
                                    imgui.ImVec2(wx, wy + offset_y),
                                    imgui.ImVec2(wx + left_w, wy + offset_y + draw_h),
                                    imgui.ImVec2(0, 0),
                                    imgui.ImVec2(1, 1),
                                )
                            if right_w > 0 and tex_id:
                                dl.add_image(
                                    imgui.ImTextureRef(tex_id),
                                    imgui.ImVec2(wx + offset_x + draw_w, wy + offset_y),
                                    imgui.ImVec2(wx + vp_w, wy + offset_y + draw_h),
                                    imgui.ImVec2(0, 0),
                                    imgui.ImVec2(1, 1),
                                )

                        dl.add_image(
                            imgui.ImTextureRef(self.canva.texture),
                            imgui.ImVec2(wx + offset_x, wy + offset_y),
                            imgui.ImVec2(wx + offset_x + draw_w, wy + offset_y + draw_h),
                            imgui.ImVec2(u0, v0),
                            imgui.ImVec2(u1, v1),
                        )

                # Add invisible button for interaction
                imgui.set_cursor_screen_pos(imgui.ImVec2(wx, wy))
                imgui.invisible_button(
                    "viewport_interaction", imgui.ImVec2(vp_w, vp_h),
                    imgui.ButtonFlags_.mouse_button_left |
                    imgui.ButtonFlags_.mouse_button_right |
                    imgui.ButtonFlags_.mouse_button_middle)

                mx = io.mouse_pos.x - wx
                my = io.mouse_pos.y - wy

                if imgui.is_item_active():
                    if self.measure_mode and imgui.is_mouse_clicked(0):
                        self.rqq.rq_select(mx, my)
                    if self.measure_mode and imgui.is_mouse_dragging(0, 0):
                        if self.canva.drag_start is None:
                            self.canva.drag_start = [mx, my]
                            self._was_dragging = True
                            self.rqq.rq_start_rotation(mx, my)
                        else:
                            self.rqq.rq_rotate(mx, my)
                    elif imgui.is_mouse_dragging(1,0):
                        if self.canva.drag_start is None:
                            self.canva.drag_start = [mx, my]
                            self._was_dragging = True
                            self.prev_pos = (mx, my)
                        else:
                            px, py = self.prev_pos
                            if px != mx or py != my:
                                self.rqq.rq_pan(mx - px, my - py)
                                self.prev_pos = (mx, my)
                    elif imgui.is_mouse_dragging(2, 0):
                        if self.canva.drag_start is None:
                            self.canva.drag_start = [mx, my]
                            self._was_dragging = True
                            self._mid_drag_start_y = my
                            self.rqq.rq_start_zoom()
                        else:
                            total_dy = self._mid_drag_start_y - my
                            if total_dy != 0:
                                self.rqq.rq_zoom(total_dy)
                    elif not self.measure_mode:
                        if imgui.is_mouse_dragging(0,0):
                            if self.canva.drag_start is None:
                                self.canva.drag_start = [mx, my]
                                self._was_dragging = True
                                self.rqq.rq_start_rotation(mx, my)
                            else:
                                self.rqq.rq_rotate(mx, my)
                else:
                    if self._was_dragging:
                        self._was_dragging = False
                        self.rqq.rq_end_interaction()
                    self.canva.drag_start = None

                    if imgui.is_item_hovered():
                        mw = io.mouse_wheel
                        if mw != 0:
                            self.rqq.rq_scroll(mw)
                        else:
                            px, py = self.prev_pos
                            if px != mx or py != my:
                                self.rqq.rq_move(mx, my)
                                self.prev_pos = (mx, my)

                imgui.set_cursor_screen_pos(imgui.ImVec2(wx, wy + vp_h))
                imgui.begin_child("viewport_toolbar", imgui.ImVec2(0, 0), False, 0)
                ViewTools.render_toolbar_content(self.rqq, self._reloading, self.canva.send_urgent)
                imgui.end_child()

                imgui.end()
                imgui.pop_style_var()

                EditorWindow.render_all(self.editor_font)

                if self._exit_pending and not self.please_stop:
                    cancel_seen = False
                    all_resolved = True
                    for ed in list(EditorWindow._instances):
                        if ed.open and not ed.close_prompt:
                            cancel_seen = True
                        if ed.close_prompt:
                            all_resolved = False
                    if cancel_seen:
                        EditorWindow._exit_in_progress = False
                        for ed in EditorWindow._instances:
                            ed.close_prompt = False
                        self._exit_pending = False
                        glfw.set_window_should_close(self.canva.window, False)
                    elif all_resolved:
                        self.please_stop = True
                        self._exit_pending = False

                self.output_view.draw("Output")
                self.console_view.draw("Console")

                if self._show_imgui_demo:
                    self._show_imgui_demo = imgui.show_demo_window(self._show_imgui_demo)
                if self._show_imgui_metrics:
                    self._show_imgui_metrics = imgui.show_metrics_window(self._show_imgui_metrics)
                if self._show_imgui_debug_log:
                    self._show_imgui_debug_log = imgui.show_debug_log_window(self._show_imgui_debug_log)
                if self._show_imgui_id_stack_tool:
                    self._show_imgui_id_stack_tool = imgui.show_id_stack_tool_window(self._show_imgui_id_stack_tool)
                if self._show_imgui_style_editor:
                    opened, self._show_imgui_style_editor = imgui.begin("ImGui Style Editor", self._show_imgui_style_editor, imgui.WindowFlags_.always_auto_resize)
                    if opened:
                        imgui.show_style_editor()
                    imgui.end()

                if self._show_about:
                    imgui.open_popup("About bcad")
                    self._show_about = False
                if imgui.begin_popup_modal("About bcad", None, imgui.WindowFlags_.always_auto_resize)[0]:
                    imgui.text("bcad — Block CAD")
                    try:
                        from bcad._version import HASH
                        imgui.text("Version: " + HASH)
                    except Exception:
                        pass
                    imgui.separator()
                    imgui.text_wrapped("A script-driven parametric CAD system based on OpenCASCADE.")
                    imgui.text_wrapped("Community fork, revived and actively developed since June 2026.")
                    imgui.spacing()
                    imgui.text("Repository: ")
                    imgui.same_line()
                    if imgui.selectable("https://asm32.info/fossil/bcad", False)[0]:
                        import webbrowser
                        webbrowser.open("https://asm32.info/fossil/bcad")
                    imgui.spacing()
                    imgui.text("ImGui " + imgui.get_version())
                    imgui.separator()
                    if imgui.button("Close"):
                        imgui.close_current_popup()
                    imgui.end_popup()

                first_frame = False
                imgui.render()
                draw_data = imgui.get_draw_data()
                imgui.backends.opengl3_render_draw_data(draw_data)
                self.canva.swap_buffers()
                self.canva.poll_events()

            imgui.save_ini_settings_to_disk(layout_path)
            dock_ids = self._exit_layout_ids if hasattr(self, '_exit_layout_ids') else EditorWindow.get_dock_ids_to_save()
            if dock_ids:
                with open(layout_path, 'a') as _f:
                    _f.write('\n[EditorDockNodes]\n')
                    _f.write(f'ids={",".join(dock_ids)}\n')
            self.canva.cleanup_gradient()
            self.canva.rq_stop()
            logging.info("GUI stopped")
            self.canva.pipe.close()
        else:
            while True:
                ep.process()
                if self.canva.pipe.poll():
                    rq = self.canva.pipe.recv()
                    jdata = json.loads(rq)
                    if jdata['rq'] == requests[RQ_LOAD_IMAGE]:
                        self.canva.call_load_image()
                    elif jdata['rq'] == requests[RQ_RELOAD_FROM_DATA]:
                        code = jdata['args'][0]
                        path = jdata['args'][1]
                        if self.canva.measure_mode:
                            self.canva.call_set_measure_mode(False)
                        Singleton.sd.reset()
                        if path:
                            Singleton.scl.path = path
                        Singleton.scl.reload_from_data(code)
                        self.canva.repaint()
                        self.canva.update_img()
                        self.canva.pipe.send(json.dumps({'rp': replies[RP_ACK]}))
                    elif jdata['rq'] == requests[RQ_START_ROTATION]:
                        self.canva.call_start_rotation(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_ROTATE]:
                        self.canva.call_rotate(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_SET_SIZE]:
                        self.canva.call_set_size(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_SCROLL]:
                        self.canva.call_scroll(jdata['args'])
                    elif jdata['rq'] == requests[RQ_MOVE]:
                        self.canva.call_move(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_PAN]:
                        self.canva.call_pan(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_CHECK_REDRAW]:
                        self.canva.call_check_redraw()
                    elif jdata['rq'] == requests[RQ_SET_MEASURE_MODE]:
                        self.canva.call_set_measure_mode(jdata['args'][0])
                    elif jdata['rq'] == requests[RQ_SELECT]:
                        self.canva.call_select(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_MEASURE_CLEAR]:
                        self.canva.call_measure_clear()
                    elif jdata['rq'] == requests[RQ_GET_OBJECT_TREE]:
                        self.canva.call_get_object_tree()
                    elif jdata['rq'] == requests[RQ_SET_VIEW]:
                        self.canva.call_set_view(jdata['args'])
                    elif jdata['rq'] == requests[RQ_SET_DISPLAY_MODE]:
                        self.canva.call_set_display_state(jdata['args'][0], jdata['args'][1])
                    elif jdata['rq'] == requests[RQ_SET_PROJECTION]:
                        self.canva.call_set_projection(jdata['args'][0])
                    elif jdata['rq'] == requests[RQ_END_INTERACTION]:
                        self.canva.call_end_interaction()
                    elif jdata['rq'] == requests[RQ_START_ZOOM]:
                        self.canva.call_start_zoom()
                    elif jdata['rq'] == requests[RQ_ZOOM]:
                        self.canva.call_zoom(jdata['args'])
                    elif jdata['rq'] == requests[RQ_VIEW_ACTION]:
                        self.canva.call_view_action(*jdata['args'])
                    elif jdata['rq'] == requests[RQ_EXPORT_GEOMETRY]:
                        Singleton.scl.export_geometry(jdata['args'][0], fmt=jdata['args'][1])
                        self.canva.pipe.send(json.dumps({'rp': replies[RP_ACK]}))
                    elif jdata['rq'] == requests[RQ_STOP]:
                        break
                time.sleep(0.01)
            self.canva.img.unlink()
            logging.info("OCCT stopped")
            self.canva.pipe.close()
