from pathlib import Path
import copy
import importlib.util
import sys

from PIL import Image, ImageDraw, ImageTk


APP = Path(__file__).resolve().parent
V30_PATH = APP / "racecore_studio_v30.py"
if str(APP) not in sys.path:
    sys.path.insert(0, str(APP))

spec = importlib.util.spec_from_file_location("racecore_v30_for_v31", V30_PATH)
v30 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(v30)

from core.keyboard_controls import action_for_key, clamped
from core.multi_selection import SelectionItem, combined_bounds, select_item


CONTROL_MASK = 0x0004


class StudioV31(v30.StudioV30):
    """Meerdere preview-objecten selecteren en als groep bewerken."""

    def __init__(self):
        self.multi_selection = []
        self.multi_drag = None
        self._multi_tree_sync = False
        super().__init__()
        self.title("RaceCore Livery Studio v3.1 - Multi Select")

    def build_layout(self):
        super().build_layout()
        tk_module = v30.v29.v28.v27.v26.v25.tk
        header = self.winfo_children()[0]
        for child in header.winfo_children():
            if isinstance(child, tk_module.Label) and "Livery Studio" in child.cget("text"):
                child.configure(text="Livery Studio  v3.1")

        self.direct_hint.configure(
            text="KLIK = SELECTEER | CTRL+KLIK = TOEVOEGEN | SLEEP = GROEP VERPLAATSEN"
        )
        viewer = self.preview_label.master
        self.multi_count = tk_module.StringVar(master=self, value="SELECTIE: 0")
        self.multi_tools = tk_module.Frame(viewer, bg="#0B1730")
        self.multi_tools.place(x=20, y=82)
        tk_module.Label(
            self.multi_tools, textvariable=self.multi_count,
            font=("Segoe UI", 8, "bold"), fg="#FDE68A", bg="#0B1730",
            padx=8, pady=5,
        ).pack(side="left")
        for label, command in (
            ("KOPIEER SELECTIE", self.duplicate_selection),
            ("WIS SELECTIE", self.delete_selection),
        ):
            tk_module.Button(
                self.multi_tools, text=label, command=command,
                font=("Segoe UI", 8, "bold"), fg="#F8FAFC", bg="#334155",
                activebackground="#475569", activeforeground="#F8FAFC",
                relief="flat", padx=8, pady=5,
            ).pack(side="left", padx=(3, 0))
        self.multi_tools.lift()

    def _sidebar_selection_changed(self, event=None):
        super()._sidebar_selection_changed(event)
        if self._multi_tree_sync:
            return
        if self.preview_selected_uid:
            self.multi_selection = [SelectionItem(
                self.preview_selected_uid, self.preview_selected_instance
            )]
        else:
            self.multi_selection = []
        self.update_multi_count()
        self.show_selection_overlay()

    def refresh_layer_sidebar(self, select_uid=None):
        preserve = list(getattr(self, "multi_selection", []))
        preserving_group = len(preserve) > 1
        if preserving_group:
            self._multi_tree_sync = True
        super().refresh_layer_sidebar(select_uid)
        if preserving_group:
            self.multi_selection = preserve
            self.update_multi_count()
            self.after_idle(self._finish_tree_sync)

    def update_multi_count(self):
        if hasattr(self, "multi_count"):
            self.multi_count.set(f"SELECTIE: {len(self.multi_selection)}")

    def new_blank_project(self):
        self.multi_selection = []
        self.multi_drag = None
        self.preview_selected_uid = None
        self.preview_selected_instance = 0
        super().new_blank_project()
        self.update_multi_count()

    def _finish_tree_sync(self):
        self._multi_tree_sync = False

    def activate_item(self, item):
        self.preview_selected_uid = item.uid
        self.preview_selected_instance = item.instance
        iid = f"layer:{item.uid}"
        tree = self.layer_sidebar_tree
        if tree.exists(iid):
            self._multi_tree_sync = True
            tree.selection_set(iid)
            tree.focus(iid)
            tree.see(iid)
            self.after_idle(self._finish_tree_sync)
        self.inline_properties.load_selected()
        self.preview_label.focus_set()

    def selection_assets(self):
        valid = []
        result = []
        for item in self.multi_selection:
            reference = self.resolve_layer_uid(item.uid)
            if reference is None:
                continue
            layer = self.layer_collections()[reference.kind][reference.index]
            if not layer.get("visible", True):
                continue
            assets = self.reference_assets(reference)
            if not assets:
                continue
            instance = min(item.instance, len(assets) - 1)
            actual = SelectionItem(item.uid, instance)
            valid.append(actual)
            result.append((actual, reference, layer, assets[instance]))
        self.multi_selection = valid
        self.update_multi_count()
        return result

    def preview_press(self, event):
        point = self.event_to_texture(event)
        if point is None:
            return
        additive = bool(getattr(event, "state", 0) & CONTROL_MASK)
        hit = self.hit_preview_object(*point)
        if additive:
            if hit is None:
                return
            reference, placed = hit
            item = SelectionItem(reference.uid, placed.instance_index)
            self.multi_selection = select_item(self.multi_selection, item, True)
            if self.multi_selection:
                self.activate_item(self.multi_selection[-1])
            else:
                self.preview_selected_uid = None
                self.preview_selected_instance = 0
            self.update_multi_count()
            self.show_selection_overlay()
            self.status.set(f"{len(self.multi_selection)} objecten geselecteerd")
            return

        if hit is None:
            self.multi_selection = []
            self.preview_selected_uid = None
            self.preview_selected_instance = 0
            self.update_multi_count()
            self.show_preview("Selectie leeggemaakt")
            return

        reference, placed = hit
        item = SelectionItem(reference.uid, placed.instance_index)
        if len(self.multi_selection) > 1 and item in self.multi_selection:
            self.activate_item(item)
            self.start_multi_drag(point)
            return

        self.multi_selection = [item]
        self.update_multi_count()
        super().preview_press(event)
        if self.preview_selected_uid:
            self.multi_selection = [SelectionItem(
                self.preview_selected_uid, self.preview_selected_instance
            )]
            self.update_multi_count()

    def start_multi_drag(self, point):
        entries = []
        boxes = []
        for item, reference, layer, placed in self.selection_assets():
            if layer.get("locked", False):
                continue
            entries.append({
                "item": item,
                "kind": reference.kind,
                "origin": self.drag_origin(reference.kind, layer, item.instance),
            })
            boxes.append(placed.bounds)
        bounds = combined_bounds(boxes)
        if not entries or bounds is None:
            return
        self.direct_drag = None
        self.multi_drag = {
            "start": point,
            "entries": entries,
            "bounds": bounds,
            "moved": False,
        }
        self.status.set(f"Groep met {len(entries)} objecten klaar om te verplaatsen")

    def snap_targets_excluding(self, excluded_uids):
        guides_x = list(v30.v29.FERRARI_GUIDES_X)
        guides_y = list(v30.v29.FERRARI_GUIDES_Y)
        collections = self.reconcile_layer_order()
        for reference in v30.v29.v28.v27.v26.v25.ordered_references(
            collections, self.layer_order
        ):
            if reference.uid in excluded_uids:
                continue
            layer = collections[reference.kind][reference.index]
            if not layer.get("visible", True):
                continue
            for placed in self.reference_assets(reference):
                left, top, right, bottom = placed.bounds
                guides_x.extend((left, placed.center_x, right))
                guides_y.extend((top, placed.center_y, bottom))
        return guides_x, guides_y

    def preview_drag(self, event):
        if self.multi_drag is None:
            super().preview_drag(event)
            return
        point = self.event_to_texture(event)
        if point is None:
            return
        raw_dx = point[0] - self.multi_drag["start"][0]
        raw_dy = point[1] - self.multi_drag["start"][1]
        left, top, right, bottom = self.multi_drag["bounds"]
        center_x, center_y = (left + right) / 2, (top + bottom) / 2
        half_width, half_height = (right - left) / 2, (bottom - top) / 2
        dx, dy = raw_dx, raw_dy
        self.active_snap_guides = {"x": None, "y": None}
        if self.snap_enabled.get():
            excluded = {entry["item"].uid for entry in self.multi_drag["entries"]}
            guides_x, guides_y = self.snap_targets_excluding(excluded)
            snap_x = v30.v29.snap_extent(center_x + raw_dx, half_width, guides_x)
            snap_y = v30.v29.snap_extent(center_y + raw_dy, half_height, guides_y)
            dx = snap_x.value - center_x
            dy = snap_y.value - center_y
            self.active_snap_guides = {"x": snap_x.guide, "y": snap_y.guide}
        dx, dy = int(round(dx)), int(round(dy))
        self.translate_entries(self.multi_drag["entries"], dx, dy)
        self.multi_drag["moved"] = self.multi_drag["moved"] or bool(dx or dy)
        self.schedule_preview()
        self.show_selection_overlay()
        self.status.set(f"Groep verplaatsen: {dx:+d}, {dy:+d}")

    def translate_entries(self, entries, dx, dy):
        for entry in entries:
            item = entry["item"]
            reference = self.resolve_layer_uid(item.uid)
            if reference is None:
                continue
            layer = self.layer_collections()[reference.kind][reference.index]
            if layer.get("locked", False):
                continue
            origin_x, origin_y = entry["origin"]
            if reference.kind == "logo":
                self.move_logo(layer, item.instance, origin_x, origin_y, dx, dy)
            else:
                layer["centerX"] = clamped(origin_x + dx, 0, 4096)
                direction = -1 if layer.get("paired", False) and item.instance > 0 else 1
                layer["centerY"] = clamped(origin_y + direction * dy, 0, 4096)

    def preview_release(self, event):
        if self.multi_drag is None:
            super().preview_release(event)
            return
        drag = self.multi_drag
        self.multi_drag = None
        self.active_snap_guides = {"x": None, "y": None}
        if not drag.get("moved"):
            return
        self.record_state()
        self.autosave()
        self.schedule_preview()
        self.refresh_layer_sidebar(self.preview_selected_uid)
        self.status.set("Groep verplaatst | Undo herstelt de hele beweging")

    def keyboard_shortcut(self, event):
        action = action_for_key(event.keysym, event.state)
        if action and action.name == "nudge" and len(self.multi_selection) > 1:
            self.nudge_selection(action.dx, action.dy)
            return "break"
        if action and action.name == "duplicate" and self.multi_selection:
            self.duplicate_selection()
            return "break"
        if action and action.name == "delete" and self.multi_selection:
            self.delete_selection()
            return "break"
        return super().keyboard_shortcut(event)

    def nudge_selection(self, dx, dy):
        entries = []
        for item, reference, layer, _placed in self.selection_assets():
            if layer.get("locked", False):
                continue
            entries.append({
                "item": item, "kind": reference.kind,
                "origin": self.drag_origin(reference.kind, layer, item.instance),
            })
        if not entries:
            self.status.set("De selectie bevat geen ontgrendelde objecten")
            return
        self.translate_entries(entries, dx, dy)
        self.record_state()
        self.autosave()
        self.schedule_preview()
        self.show_selection_overlay()
        self.status.set(f"{len(entries)} objecten fijn verplaatst: {dx:+d}, {dy:+d}")

    def duplicate_selection(self):
        new_items = []
        for item in list(self.multi_selection):
            reference = self.resolve_layer_uid(item.uid)
            if reference is None:
                continue
            collection = self.layer_collections()[reference.kind]
            layer = copy.deepcopy(collection[reference.index])
            layer.pop("layerUid", None)
            v30.v29.v28.v27.v26.v25.ensure_uid(layer, reference.kind)
            label = v30.v29.v28.v27.v26.v25.KIND_LABELS[reference.kind]
            layer["name"] = f"{layer.get('name', label)} kopie"
            layer["locked"] = False
            if reference.kind in {"shape", "stripe", "text"}:
                layer["centerX"] = clamped(layer.get("centerX", 2000) + 70, 0, 4096)
                layer["centerY"] = clamped(layer.get("centerY", 2000) + 70, 0, 4096)
            else:
                layer["offsetX"] = int(layer.get("offsetX", 0)) + 70
                layer["offsetY"] = int(layer.get("offsetY", 0)) + 70
            collection.insert(reference.index + 1, layer)
            old_position = self.layer_order.index(reference.uid)
            self.layer_order.insert(old_position + 1, layer["layerUid"])
            new_items.append(SelectionItem(layer["layerUid"], item.instance))
        if not new_items:
            self.status.set("Kies eerst een object om te dupliceren")
            return
        self.multi_selection = new_items
        self.activate_item(new_items[-1])
        self.record_state()
        self.autosave()
        self.schedule_preview()
        self.refresh_layer_sidebar(new_items[-1].uid)
        self.update_multi_count()
        self.status.set(f"{len(new_items)} objecten gedupliceerd")

    def delete_selection(self):
        deleted = 0
        for item in list(self.multi_selection):
            reference = self.resolve_layer_uid(item.uid)
            if reference is None:
                continue
            collection = self.layer_collections()[reference.kind]
            if collection[reference.index].get("locked", False):
                continue
            collection.pop(reference.index)
            self.layer_order = v30.v29.v28.v27.v26.v25.without_uid(
                self.layer_order, reference.uid
            )
            deleted += 1
        if not deleted:
            self.status.set("Vergrendelde objecten zijn niet verwijderd")
            return
        self.multi_selection = []
        self.preview_selected_uid = None
        self.preview_selected_instance = 0
        self.record_state()
        self.autosave()
        self.schedule_preview()
        self.refresh_layer_sidebar()
        self.update_multi_count()
        self.status.set(f"{deleted} objecten verwijderd")

    def show_selection_overlay(self):
        if len(self.multi_selection) <= 1:
            super().show_selection_overlay()
            self.update_multi_count()
            return
        path = self.pipeline.paths.generated / "preview.png"
        assets = self.selection_assets()
        if not path.exists() or not assets:
            return
        try:
            with Image.open(path) as opened:
                image = opened.convert("RGB")
            draw = ImageDraw.Draw(image)
            scale_x, scale_y = image.width / 4096, image.height / 4096
            boxes = []
            for item, _reference, _layer, placed in assets:
                left, top, right, bottom = placed.bounds
                boxes.append((left, top, right, bottom))
                box = tuple(round(value * (scale_x if index % 2 == 0 else scale_y))
                            for index, value in enumerate((left, top, right, bottom)))
                colour = "#FDE68A" if item.uid == self.preview_selected_uid else "#22D3EE"
                draw.rectangle(box, outline=colour, width=2)
            group = combined_bounds(boxes)
            if group:
                group_box = (
                    round(group[0] * scale_x), round(group[1] * scale_y),
                    round(group[2] * scale_x), round(group[3] * scale_y),
                )
                for x in range(group_box[0], group_box[2], 18):
                    draw.line((x, group_box[1], min(x + 10, group_box[2]), group_box[1]), fill="#FBBF24", width=3)
                    draw.line((x, group_box[3], min(x + 10, group_box[2]), group_box[3]), fill="#FBBF24", width=3)
                for y in range(group_box[1], group_box[3], 18):
                    draw.line((group_box[0], y, group_box[0], min(y + 10, group_box[3])), fill="#FBBF24", width=3)
                    draw.line((group_box[2], y, group_box[2], min(y + 10, group_box[3])), fill="#FBBF24", width=3)
            self.draw_additional_guides(draw, image, scale_x, scale_y)
            self.preview_photo = ImageTk.PhotoImage(image)
            self.preview_label.configure(image=self.preview_photo, text="")
            for widget in (self.direct_hint, self.keyboard_hint, self.multi_tools, self.snap_tools):
                widget.lift()
        except (OSError, ValueError):
            pass


if __name__ == "__main__":
    StudioV31().mainloop()
