from pathlib import Path
from PIL import Image, ImageTk, ImageChops, ImageDraw, ImageFont, ImageColor
import copy
import importlib.util
import json
import os
import subprocess
import sys
import tkinter as tk
from tkinter import colorchooser, ttk


ROOT = Path(__file__).resolve().parents[2]
APP = Path(__file__).resolve().parent
GENERATED = APP / 'generated'
AUTOSAVE = APP / 'autosave.racecore.json'
LIVE_CONFIG = APP / 'live-link-ui.json'
V16_PATH = ROOT / 'outputs' / 'RaceCore_Studio_v1.6' / 'racecore_studio_v16.py'
V16_AUTOSAVE = V16_PATH.parent / 'autosave.racecore.json'

spec = importlib.util.spec_from_file_location('racecore_v16', V16_PATH)
v16 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(v16)

# Alle geerfde motoren schrijven naar de v1.7-map. De logo-bibliotheek blijft
# bewust in v1.6 staan, zodat bestaande geimporteerde logo's behouden blijven.
for module in (
    v16, v16.v15, v16.v15.v14, v16.v15.v14.v13,
    v16.v15.v14.v13.v12, v16.v15.v14.v13.v12.v11,
):
    module.APP = APP
    module.GENERATED = GENERATED
    module.AUTOSAVE = AUTOSAVE
    module.LIVE_CONFIG = LIVE_CONFIG

COLORS = v16.COLORS
QUICK_AREAS = v16.v15.v14.QUICK_AREAS
FONT_CHOICES = ('Modern Bold', 'Modern', 'Race Condensed', 'Impact', 'Classic')


def resolve_font(choice, size):
    fonts = Path(os.environ.get('WINDIR', r'C:\Windows')) / 'Fonts'
    names = {
        'Modern Bold': ('segoeuib.ttf', 'arialbd.ttf'),
        'Modern': ('segoeui.ttf', 'arial.ttf'),
        'Race Condensed': ('bahnschrift.ttf', 'arialn.ttf', 'arialbd.ttf'),
        'Impact': ('impact.ttf', 'arialbd.ttf'),
        'Classic': ('arial.ttf', 'segoeui.ttf'),
    }
    for filename in names.get(choice, names['Modern Bold']):
        path = fonts / filename
        if path.exists():
            return ImageFont.truetype(str(path), max(8, int(size)))
    try:
        return ImageFont.truetype('DejaVuSans-Bold.ttf', max(8, int(size)))
    except OSError:
        return ImageFont.load_default()


def text_asset(layer):
    text = str(layer.get('text', '')).strip() or ' '
    size = max(20, int(layer.get('fontSize', 180)))
    outline = max(0, int(layer.get('outlineWidth', 6)))
    opacity = max(5, min(100, int(layer.get('opacity', 100))))
    font = resolve_font(layer.get('font', 'Modern Bold'), size)
    fill_rgb = ImageColor.getrgb(layer.get('colour', '#FFFFFF'))
    stroke_rgb = ImageColor.getrgb(layer.get('outlineColour', '#08152C'))
    alpha = round(255 * opacity / 100)
    probe = Image.new('RGBA', (8, 8), (0, 0, 0, 0))
    draw = ImageDraw.Draw(probe)
    box = draw.textbbox((0, 0), text, font=font, stroke_width=outline)
    width, height = max(1, box[2] - box[0]), max(1, box[3] - box[1])
    pad = max(12, outline + round(size * .08))
    image = Image.new('RGBA', (width + pad * 2, height + pad * 2), (0, 0, 0, 0))
    draw = ImageDraw.Draw(image)
    draw.text(
        (pad - box[0], pad - box[1]), text, font=font,
        fill=(*fill_rgb, alpha), stroke_width=outline,
        stroke_fill=(*stroke_rgb, alpha),
    )
    return image.rotate(
        float(layer.get('rotation', 0)), expand=True,
        resample=Image.Resampling.BICUBIC,
    )


class TextPlacementEditor:
    def __init__(self, studio, index, is_new=False):
        self.studio = studio
        self.index = index
        self.is_new = is_new
        self.layer = copy.deepcopy(studio.normalise_text(studio.text_layers[index]))
        self.background = studio.editor_background(exclude_text=index)
        self.texture_size = self.background.size
        self.editable = studio.editable_mask(self.texture_size)
        self.win = tk.Toplevel(studio)
        self.win.title('RaceCore Tekst & Nummer Editor')
        self.win.geometry('1240x860')
        self.win.minsize(1000, 720)
        self.win.configure(bg=COLORS.NAVY)
        self.win.transient(studio)
        self.win.grab_set()
        self.win.protocol('WM_DELETE_WINDOW', self.cancel)

        self.canvas = tk.Canvas(self.win, bg='#030914', highlightthickness=0,
                                cursor='crosshair')
        self.canvas.pack(side='left', fill='both', expand=True, padx=(16, 8), pady=16)
        self.controls = tk.Frame(self.win, bg=COLORS.PANEL, width=310)
        self.controls.pack(side='right', fill='y', padx=(8, 16), pady=16)
        self.controls.pack_propagate(False)

        self.name = tk.StringVar(value=self.layer['name'])
        self.text = tk.StringVar(value=self.layer['text'])
        self.area = tk.StringVar(value='Motorkap')
        self.font = tk.StringVar(value=self.layer['font'])
        self.colour = tk.StringVar(value=self.layer['colour'])
        self.outline_colour = tk.StringVar(value=self.layer['outlineColour'])
        self.font_size = tk.IntVar(value=self.layer['fontSize'])
        self.outline_width = tk.IntVar(value=self.layer['outlineWidth'])
        self.rotation = tk.IntVar(value=self.layer['rotation'])
        self.opacity = tk.IntVar(value=self.layer['opacity'])
        self.paired = tk.BooleanVar(value=self.layer['paired'])
        self.coord_text = tk.StringVar()
        self.photo = None
        self.display_scale = 1.0
        self.display_origin = (0, 0)
        self.display_cache_key = None
        self.display_background = None
        self.display_editable = None
        self.dragging = False
        self.build_controls()
        self.bind_events()
        self.text.trace_add('write', lambda *_args: self.redraw())
        self.win.after(60, self.redraw)

    def label(self, text):
        tk.Label(self.controls, text=text, font=('Segoe UI', 9), fg=COLORS.TEXT,
                 bg=COLORS.PANEL).pack(anchor='w', padx=18, pady=(2, 3))

    def slider(self, title, variable, low, high):
        row = tk.Frame(self.controls, bg=COLORS.PANEL)
        row.pack(fill='x', padx=18, pady=(1, 0))
        tk.Label(row, text=title, font=('Segoe UI', 9), fg=COLORS.TEXT,
                 bg=COLORS.PANEL).pack(side='left')
        tk.Label(row, textvariable=variable, font=('Segoe UI', 9, 'bold'),
                 fg=COLORS.CYAN, bg=COLORS.PANEL).pack(side='right')
        tk.Scale(
            self.controls, from_=low, to=high, variable=variable,
            command=lambda _value: self.redraw(), orient='horizontal',
            bg=COLORS.PANEL, fg=COLORS.TEXT, troughcolor='#253656',
            activebackground=COLORS.CYAN, highlightthickness=0, showvalue=False,
        ).pack(fill='x', padx=14)

    def build_controls(self):
        tk.Label(self.controls, text='TEKST / NUMMER', font=('Segoe UI', 14, 'bold'),
                 fg=COLORS.CYAN, bg=COLORS.PANEL).pack(anchor='w', padx=18,
                                                       pady=(14, 5))
        self.label('Laagnaam')
        tk.Entry(self.controls, textvariable=self.name, font=('Segoe UI', 10)).pack(
            fill='x', padx=18, pady=(0, 5))
        self.label('Tekst op de auto')
        tk.Entry(self.controls, textvariable=self.text, font=('Segoe UI', 12, 'bold')).pack(
            fill='x', padx=18, pady=(0, 7))
        self.label('Snelle plaatsing')
        area = ttk.Combobox(self.controls, textvariable=self.area,
                            values=list(QUICK_AREAS), state='readonly')
        area.pack(fill='x', padx=18, pady=(0, 7))
        area.bind('<<ComboboxSelected>>', self.choose_area)
        self.label('Lettertype')
        font = ttk.Combobox(self.controls, textvariable=self.font,
                            values=FONT_CHOICES, state='readonly')
        font.pack(fill='x', padx=18, pady=(0, 7))
        font.bind('<<ComboboxSelected>>', lambda _event: self.redraw())

        colour_row = tk.Frame(self.controls, bg=COLORS.PANEL)
        colour_row.pack(fill='x', padx=18, pady=(0, 5))
        self.colour_button = tk.Button(colour_row, text='Tekstkleur', command=self.pick_colour,
                                       font=('Segoe UI', 9, 'bold'), relief='flat', pady=6)
        self.colour_button.pack(side='left', fill='x', expand=True, padx=(0, 3))
        self.outline_button = tk.Button(colour_row, text='Randkleur',
                                        command=self.pick_outline,
                                        font=('Segoe UI', 9, 'bold'), relief='flat', pady=6)
        self.outline_button.pack(side='left', fill='x', expand=True, padx=(3, 0))
        self.update_colour_buttons()
        self.slider('Grootte', self.font_size, 30, 700)
        self.slider('Randdikte', self.outline_width, 0, 40)
        self.slider('Rotatie', self.rotation, -180, 180)
        self.slider('Dekking', self.opacity, 5, 100)
        tk.Checkbutton(
            self.controls, text='Kopie op andere zijkant', variable=self.paired,
            command=self.redraw, font=('Segoe UI', 9, 'bold'), fg=COLORS.TEXT,
            bg=COLORS.PANEL, selectcolor='#253656', activebackground=COLORS.PANEL,
            activeforeground=COLORS.TEXT,
        ).pack(anchor='w', padx=14, pady=(4, 0))
        tk.Label(self.controls, textvariable=self.coord_text, font=('Consolas', 9),
                 fg='#93C5FD', bg=COLORS.PANEL).pack(anchor='w', padx=18, pady=(3, 0))
        tk.Label(
            self.controls,
            text='Klik of sleep om te plaatsen.\nMuiswiel = grootte | Shift + wiel = draaien\nPijltjes = nauwkeurig verplaatsen',
            font=('Segoe UI', 8), fg=COLORS.MUTED, bg=COLORS.PANEL, justify='left',
        ).pack(anchor='w', padx=18, pady=3)
        tk.Frame(self.controls, bg=COLORS.PANEL).pack(fill='both', expand=True)
        tk.Button(self.controls, text='ANNULEREN', command=self.cancel,
                  font=('Segoe UI', 9, 'bold'), fg=COLORS.TEXT, bg='#475569',
                  relief='flat', pady=7).pack(fill='x', padx=18, pady=(0, 5))
        tk.Button(self.controls, text='OPSLAAN & PREVIEW', command=self.save,
                  font=('Segoe UI', 10, 'bold'), fg=COLORS.NAVY, bg=COLORS.CYAN,
                  relief='flat', pady=10).pack(fill='x', padx=18, pady=(0, 14))

    @staticmethod
    def button_colours(colour):
        r, g, b = ImageColor.getrgb(colour)
        return ('#000000' if r * 299 + g * 587 + b * 114 > 150000 else '#FFFFFF')

    def update_colour_buttons(self):
        for button, colour in ((self.colour_button, self.colour.get()),
                               (self.outline_button, self.outline_colour.get())):
            fg = self.button_colours(colour)
            button.configure(bg=colour, fg=fg, activebackground=colour,
                             activeforeground=fg)

    def pick_colour(self):
        colour = colorchooser.askcolor(self.colour.get(), title='Kies tekstkleur')[1]
        if colour:
            self.colour.set(colour.upper())
            self.update_colour_buttons()
            self.redraw()

    def pick_outline(self):
        colour = colorchooser.askcolor(self.outline_colour.get(), title='Kies randkleur')[1]
        if colour:
            self.outline_colour.set(colour.upper())
            self.update_colour_buttons()
            self.redraw()

    def bind_events(self):
        self.canvas.bind('<Configure>', lambda _event: self.redraw())
        self.canvas.bind('<Button-1>', self.pointer_down)
        self.canvas.bind('<B1-Motion>', self.pointer_move)
        self.canvas.bind('<ButtonRelease-1>', lambda _event: setattr(self, 'dragging', False))
        self.canvas.bind('<MouseWheel>', self.mouse_wheel)
        self.win.bind('<Left>', lambda event: self.nudge(-self.nudge_size(event), 0))
        self.win.bind('<Right>', lambda event: self.nudge(self.nudge_size(event), 0))
        self.win.bind('<Up>', lambda event: self.nudge(0, -self.nudge_size(event)))
        self.win.bind('<Down>', lambda event: self.nudge(0, self.nudge_size(event)))
        self.win.bind('<Escape>', lambda _event: self.cancel())
        self.win.bind('<Control-Return>', lambda _event: self.save())

    @staticmethod
    def nudge_size(event):
        return 32 if event.state & 0x0001 else 8

    def choose_area(self, _event=None):
        x, y, rotation, paired = QUICK_AREAS[self.area.get()]
        self.layer['centerX'], self.layer['centerY'] = x, y
        self.rotation.set(rotation)
        self.paired.set(paired)
        self.redraw()

    def pointer_to_uv(self, event):
        ox, oy = self.display_origin
        return ((event.x - ox) / self.display_scale,
                (event.y - oy) / self.display_scale)

    def set_center(self, x, y):
        w, h = self.texture_size
        self.layer['centerX'] = max(0, min(w, round(x)))
        self.layer['centerY'] = max(0, min(h, round(y)))
        self.redraw()

    def pointer_down(self, event):
        self.dragging = True
        self.set_center(*self.pointer_to_uv(event))

    def pointer_move(self, event):
        if self.dragging:
            self.set_center(*self.pointer_to_uv(event))

    def mouse_wheel(self, event):
        direction = 1 if event.delta > 0 else -1
        if event.state & 0x0001:
            self.rotation.set(max(-180, min(180, self.rotation.get() + direction * 5)))
        else:
            self.font_size.set(max(30, min(700, self.font_size.get() + direction * 10)))
        self.redraw()

    def nudge(self, dx, dy):
        self.set_center(self.layer['centerX'] + dx, self.layer['centerY'] + dy)

    def working_layer(self):
        layer = copy.deepcopy(self.layer)
        layer.update({
            'name': self.name.get().strip() or 'Tekstlaag',
            'text': self.text.get().strip() or 'TEKST', 'font': self.font.get(),
            'colour': self.colour.get(), 'outlineColour': self.outline_colour.get(),
            'fontSize': self.font_size.get(), 'outlineWidth': self.outline_width.get(),
            'rotation': self.rotation.get(), 'opacity': self.opacity.get(),
            'paired': self.paired.get(),
        })
        return layer

    def compose_display(self, display_w, display_h):
        key = (display_w, display_h)
        if key != self.display_cache_key:
            self.display_cache_key = key
            self.display_background = self.background.resize(key, Image.Resampling.LANCZOS)
            self.display_editable = self.editable.resize(key, Image.Resampling.LANCZOS)
        canvas = self.display_background.copy()
        bounds = None
        layer = self.working_layer()
        for cx, cy, rotation in v16.v15.v14.v13.stripe_instances(layer, self.texture_size):
            rendered = copy.deepcopy(layer)
            rendered['rotation'] = rotation
            item = text_asset(rendered)
            x, y = round(cx - item.width / 2), round(cy - item.height / 2)
            shown = item.resize((max(1, round(item.width * self.display_scale)),
                                 max(1, round(item.height * self.display_scale))),
                                Image.Resampling.LANCZOS)
            overlay = Image.new('RGBA', key, (0, 0, 0, 0))
            overlay.alpha_composite(shown, (round(x * self.display_scale),
                                            round(y * self.display_scale)))
            alpha = ImageChops.multiply(overlay.getchannel('A'), self.display_editable)
            overlay.putalpha(alpha)
            canvas = Image.alpha_composite(canvas, overlay)
            if bounds is None:
                bounds = (x, y, x + item.width, y + item.height)
        return canvas, bounds

    def redraw(self):
        try:
            if not self.canvas.winfo_exists():
                return
        except tk.TclError:
            return
        canvas_w, canvas_h = max(300, self.canvas.winfo_width()), max(300, self.canvas.winfo_height())
        texture_w, texture_h = self.texture_size
        self.display_scale = min((canvas_w - 36) / texture_w, (canvas_h - 36) / texture_h)
        display_w, display_h = max(1, round(texture_w * self.display_scale)), max(1, round(texture_h * self.display_scale))
        ox, oy = round((canvas_w - display_w) / 2), round((canvas_h - display_h) / 2)
        self.display_origin = (ox, oy)
        display, bounds = self.compose_display(display_w, display_h)
        self.photo = ImageTk.PhotoImage(display)
        self.canvas.delete('all')
        self.canvas.create_image(ox, oy, image=self.photo, anchor='nw')
        self.canvas.create_rectangle(ox, oy, ox + display_w, oy + display_h,
                                     outline='#1E3A5F')
        if bounds:
            x0, y0, x1, y1 = bounds
            self.canvas.create_rectangle(ox + x0 * self.display_scale,
                                         oy + y0 * self.display_scale,
                                         ox + x1 * self.display_scale,
                                         oy + y1 * self.display_scale,
                                         outline=COLORS.CYAN, width=2, dash=(6, 3))
        cx, cy = self.layer['centerX'], self.layer['centerY']
        sx, sy = ox + cx * self.display_scale, oy + cy * self.display_scale
        self.canvas.create_line(sx - 8, sy, sx + 8, sy, fill='#FFFFFF', width=2)
        self.canvas.create_line(sx, sy - 8, sx, sy + 8, fill='#FFFFFF', width=2)
        self.coord_text.set(f'Positie: X {cx} / Y {cy}')

    def save(self):
        self.studio.text_layers[self.index] = self.working_layer()
        self.is_new = False
        self.win.destroy()
        self.studio.commit_texts(self.index, 'Tekst opgeslagen')

    def cancel(self):
        if self.is_new and self.index < len(self.studio.text_layers):
            self.studio.text_layers.pop(self.index)
            self.studio.refresh_texts(max(0, self.index - 1))
        self.win.destroy()


class StudioV17(v16.StudioV16):
    def __init__(self):
        self.text_layers = []
        migrate = not AUTOSAVE.exists() and V16_AUTOSAVE.exists()
        super().__init__()
        self.title('RaceCore Livery Studio v1.7 - Text & Numbers')
        if migrate:
            self.load_path(V16_AUTOSAVE, record=False)
            self.autosave()

    def build_layout(self):
        super().build_layout()
        header = self.winfo_children()[0]
        for child in header.winfo_children():
            if isinstance(child, tk.Label) and 'Livery Studio' in child.cget('text'):
                child.configure(text='Livery Studio  v1.7')
        toolbar = self.winfo_children()[1]
        tk.Button(toolbar, text='Tekst / nr.', command=self.text_dialog,
                  font=('Segoe UI', 9, 'bold'), fg=COLORS.NAVY, bg='#FDBA74',
                  activebackground='#FED7AA', relief='flat', padx=10).pack(
                      side='left', padx=(0, 8), pady=6)

    @staticmethod
    def normalise_text(layer):
        defaults = {
            'id': 'text', 'name': 'Teamnaam', 'text': 'TEAM NAME',
            'font': 'Modern Bold', 'colour': '#FFFFFF',
            'outlineColour': '#08152C', 'fontSize': 180, 'outlineWidth': 6,
            'rotation': 0, 'centerX': 2050, 'centerY': 1220,
            'opacity': 100, 'paired': True, 'visible': True, 'locked': False,
        }
        for key, value in defaults.items():
            layer.setdefault(key, value)
        return layer

    def snapshot(self):
        state = super().snapshot()
        state['textLayers'] = copy.deepcopy(self.text_layers)
        return state

    def apply_state(self, state, record=False):
        state = copy.deepcopy(state)
        self.text_layers = [self.normalise_text(item)
                            for item in state.get('textLayers', [])]
        super().apply_state(state, record)
        if self.live_widget(self, 'text_list'):
            self.refresh_texts()

    def new_blank_project(self):
        self.text_layers = []
        super().new_blank_project()

    def text_dialog(self):
        win = tk.Toplevel(self)
        win.title('RaceCore Tekst & Nummers')
        win.geometry('860x580')
        win.minsize(780, 520)
        win.configure(bg=COLORS.PANEL)
        win.transient(self)
        win.grab_set()
        top = tk.Frame(win, bg=COLORS.PANEL)
        top.pack(fill='x', padx=22, pady=(18, 10))
        tk.Label(top, text='TEKST & NUMMERS', font=('Segoe UI', 15, 'bold'),
                 fg=COLORS.CYAN, bg=COLORS.PANEL).pack(side='left')
        self.text_summary = tk.StringVar()
        tk.Label(top, textvariable=self.text_summary, font=('Segoe UI', 9),
                 fg=COLORS.MUTED, bg=COLORS.PANEL).pack(side='right')
        frame = tk.Frame(win, bg=COLORS.PANEL)
        frame.pack(fill='both', expand=True, padx=22)
        self.text_list = tk.Listbox(frame, font=('Segoe UI', 10), bg='#08152C',
                                    fg=COLORS.TEXT, selectbackground='#EA580C',
                                    relief='flat', activestyle='none',
                                    exportselection=False)
        self.text_list.pack(side='left', fill='both', expand=True)
        self.text_list.bind('<Double-Button-1>', lambda _event: self.edit_text())
        self.text_list.bind('<Delete>', lambda _event: self.delete_text())
        self.text_list.bind('<space>', lambda _event: self.toggle_text())
        self.text_list.bind('<Control-d>', lambda _event: self.duplicate_text())
        side = tk.Frame(frame, bg=COLORS.PANEL, width=190)
        side.pack(side='right', fill='y', padx=(12, 0))
        actions = [
            ('+ Teamnaam', lambda: self.add_text('team'), COLORS.CYAN),
            ('+ Startnummer', lambda: self.add_text('number'), '#FDBA74'),
            ('Visueel bewerken', self.edit_text, '#334155'),
            ('Dupliceren', self.duplicate_text, '#334155'),
            ('Andere zijde aan/uit', self.pair_text, '#334155'),
            ('Naar voren', lambda: self.move_text(1), '#334155'),
            ('Naar achteren', lambda: self.move_text(-1), '#334155'),
            ('Tonen / verbergen', self.toggle_text, '#334155'),
            ('Vergrendelen', self.lock_text, '#334155'),
            ('Verwijderen', self.delete_text, '#7F1D1D'),
        ]
        for title, command, colour in actions:
            tk.Button(side, text=title, command=command,
                      font=('Segoe UI', 9, 'bold'),
                      fg=(COLORS.NAVY if colour in (COLORS.CYAN, '#FDBA74') else '#F8FAFC'),
                      bg=colour, relief='flat', pady=6).pack(fill='x', pady=(0, 4))
        tk.Label(win,
                 text='Tekst blijft scherp en komt automatisch boven kleurvlakken en strepen.',
                 font=('Segoe UI', 9), fg=COLORS.MUTED, bg=COLORS.PANEL).pack(
                     anchor='w', padx=22, pady=(8, 0))
        tk.Button(win, text='SLUITEN', command=win.destroy,
                  font=('Segoe UI', 10, 'bold'), fg=COLORS.NAVY, bg=COLORS.CYAN,
                  relief='flat', pady=10).pack(fill='x', padx=22, pady=16)
        self.refresh_texts()
        self.text_list.focus_set()

    def selected_text(self):
        if not self.live_widget(self, 'text_list'):
            return None
        selected = self.text_list.curselection()
        return selected[0] if selected else None

    def refresh_texts(self, select=None):
        if not self.live_widget(self, 'text_list'):
            if hasattr(self, 'text_list'):
                delattr(self, 'text_list')
            return
        self.text_list.delete(0, 'end')
        visible = 0
        for index, raw in enumerate(self.text_layers):
            layer = self.normalise_text(raw)
            visible += int(layer['visible'])
            eye = '[aan]' if layer['visible'] else '[uit]'
            paired = ' | andere zijde' if layer['paired'] else ''
            locked = ' | vergrendeld' if layer['locked'] else ''
            self.text_list.insert('end', f"{eye}  {index + 1:02d}  {layer['name']}  |  "
                                         f"{layer['text']}{paired}{locked}")
        self.text_summary.set(f'{len(self.text_layers)} lagen | {visible} zichtbaar')
        if self.text_layers:
            index = min(select if select is not None else len(self.text_layers) - 1,
                        len(self.text_layers) - 1)
            self.text_list.selection_set(index)
            self.text_list.see(index)

    def commit_texts(self, select=None, label='Tekst bijgewerkt'):
        self.refresh_texts(select)
        self.status.set(label)
        self.changed()

    def add_text(self, kind='team'):
        number = kind == 'number'
        layer = self.normalise_text({
            'id': f'text-{len(self.text_layers) + 1}',
            'name': 'Startnummer' if number else 'Teamnaam',
            'text': '99' if number else 'TEAM NAME',
            'font': 'Impact' if number else 'Modern Bold',
            'fontSize': 340 if number else 180,
            'outlineWidth': 12 if number else 6,
            'colour': self.accent.get() if number else '#FFFFFF',
        })
        self.text_layers.append(layer)
        index = len(self.text_layers) - 1
        self.refresh_texts(index)
        TextPlacementEditor(self, index, is_new=True)

    def edit_text(self):
        index = self.selected_text()
        if index is None:
            self.status.set('Kies eerst een tekstlaag')
            return
        if self.normalise_text(self.text_layers[index])['locked']:
            self.status.set('Ontgrendel deze tekst eerst')
            return
        TextPlacementEditor(self, index)

    def duplicate_text(self):
        index = self.selected_text()
        if index is None:
            return
        layer = copy.deepcopy(self.normalise_text(self.text_layers[index]))
        layer['id'] = f"{layer['id']}-copy"
        layer['name'] += ' kopie'
        layer['centerX'] = min(4096, layer['centerX'] + 80)
        layer['centerY'] = min(4096, layer['centerY'] + 80)
        layer['locked'] = False
        self.text_layers.insert(index + 1, layer)
        self.commit_texts(index + 1, 'Tekst gedupliceerd')

    def pair_text(self):
        index = self.selected_text()
        if index is None:
            return
        layer = self.normalise_text(self.text_layers[index])
        if layer['locked']:
            self.status.set('Ontgrendel deze tekst eerst')
            return
        layer['paired'] = not layer['paired']
        self.commit_texts(index, 'Andere zijde aangepast')

    def toggle_text(self):
        index = self.selected_text()
        if index is None:
            return
        layer = self.normalise_text(self.text_layers[index])
        layer['visible'] = not layer['visible']
        self.commit_texts(index, 'Zichtbaarheid aangepast')

    def lock_text(self):
        index = self.selected_text()
        if index is None:
            return
        layer = self.normalise_text(self.text_layers[index])
        layer['locked'] = not layer['locked']
        self.commit_texts(index, 'Vergrendeling aangepast')

    def delete_text(self):
        index = self.selected_text()
        if index is None:
            return
        if self.normalise_text(self.text_layers[index])['locked']:
            self.status.set('Een vergrendelde tekst kan niet worden verwijderd')
            return
        self.text_layers.pop(index)
        self.commit_texts(max(0, index - 1), 'Tekst verwijderd')

    def move_text(self, delta):
        index = self.selected_text()
        if index is None:
            return
        if self.normalise_text(self.text_layers[index])['locked']:
            self.status.set('Ontgrendel deze tekst eerst')
            return
        target = max(0, min(len(self.text_layers) - 1, index + delta))
        if target != index:
            self.text_layers.insert(target, self.text_layers.pop(index))
            self.commit_texts(target, 'Laagvolgorde aangepast')

    def compose_texts(self, canvas, exclude=None):
        editable = self.editable_mask(canvas.size)
        for index, raw in enumerate(self.text_layers):
            if index == exclude:
                continue
            layer = self.normalise_text(raw)
            if not layer['visible']:
                continue
            for cx, cy, rotation in v16.v15.v14.v13.stripe_instances(layer, canvas.size):
                rendered = copy.deepcopy(layer)
                rendered['rotation'] = rotation
                canvas = self.paste_item(canvas, text_asset(rendered), cx, cy, editable)
        return canvas

    def editor_background(self, exclude_stripe=None, exclude_shape=None,
                          exclude_logo=None, exclude_text=None):
        candidates = [
            v16.v15.v14.v13.v12.v11.v10.v09.GENERATED / 'customskin.tga',
            v16.v15.v14.v13.v12.v11.v10.v09.v08.GENERATED / 'customskin.tga',
            v16.v15.v14.v13.v12.v11.v10.v09.v08.base.SOURCE,
        ]
        for path in candidates:
            if path.exists():
                canvas = Image.open(path).convert('RGBA')
                break
        else:
            canvas = Image.new('RGBA', (4096, 4096), (6, 15, 31, 255))
        canvas = self.compose_shapes(canvas, exclude=exclude_shape)
        canvas = self.compose_stripes(canvas, exclude=exclude_stripe)
        canvas = self.compose_texts(canvas, exclude=exclude_text)
        return self.compose_logos(canvas, exclude=exclude_logo)

    def _worker(self, apply):
        try:
            old_enabled = self.logo_enabled
            self.logo_enabled = False
            try:
                self.suppressed_preview_callbacks += 2
                v16.v15.v14.v13.v12.v11.v10.v09.StudioV09._worker(self, False)
            finally:
                self.logo_enabled = old_enabled
            source = v16.v15.v14.v13.v12.v11.v10.v09.GENERATED / 'customskin.tga'
            canvas = Image.open(source).convert('RGBA')
            canvas = self.compose_shapes(canvas)
            canvas = self.compose_stripes(canvas)
            canvas = self.compose_texts(canvas)
            canvas = self.compose_logos(canvas)
            GENERATED.mkdir(parents=True, exist_ok=True)
            rgb = canvas.convert('RGB')
            rgb.save(GENERATED / 'customskin.tga', format='TGA', compression=None)
            Image.new('RGB', canvas.size, (0, 0, 0)).save(
                GENERATED / 'customskin_region.tga', format='TGA', compression=None)
            preview = rgb.copy()
            preview.thumbnail((880, 680))
            preview.save(GENERATED / 'preview.png', optimize=True)
            self.autosave()
            label = (f'Preview gereed | {len(self.shape_layers)} kleurvlakken | '
                     f'{len(self.stripe_layers)} strepen | {len(self.text_layers)} teksten | '
                     f'{len(self.logo_layers)} logos')
            if apply:
                result = subprocess.run(
                    [sys.executable,
                     str(v16.v15.v14.v13.v12.v11.v10.v09.v08.base.LIVE_LINK),
                     str(LIVE_CONFIG)], capture_output=True, text=True, timeout=60,
                )
                if result.returncode:
                    raise RuntimeError(result.stderr.strip() or result.stdout.strip())
                event = json.loads(result.stdout)
                label = 'Live in LMU' if event['status'] == 'installed' else 'LMU is al bijgewerkt'
            self.after(0, lambda: self.show_preview(label))
        except Exception as error:
            self.after(0, lambda text=str(error): self.show_error(text))


if __name__ == '__main__':
    StudioV17().mainloop()
