from pathlib import Path
import copy
import importlib.util
import json
import subprocess
import sys
import tkinter as tk
from tkinter import colorchooser, ttk

from PIL import Image, ImageChops, ImageDraw, ImageFilter


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'
V22_PATH = ROOT / 'outputs' / 'RaceCore_Studio_v2.2' / 'racecore_studio_v22.py'
V22_AUTOSAVE = V22_PATH.parent / 'autosave.racecore.json'
OFFICIAL_LMU_TEMPLATE = APP / 'assets' / 'Template_GT3_Ferrari296LMGT3_EVO_2026.psd'
FRESH_TEMPLATE = OFFICIAL_LMU_TEMPLATE
FRESH_BLANK = OFFICIAL_LMU_TEMPLATE

spec = importlib.util.spec_from_file_location('racecore_v22_for_v23', V22_PATH)
v22 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(v22)

for module in (
    v22, v22.v20, v22.v20.v19, v22.v20.v19.v18,
    v22.v20.v19.v18.v17, v22.v20.v19.v18.v17.v16,
    v22.v20.v19.v18.v17.v16.v15,
    v22.v20.v19.v18.v17.v16.v15.v14,
    v22.v20.v19.v18.v17.v16.v15.v14.v13,
    v22.v20.v19.v18.v17.v16.v15.v14.v13.v12,
    v22.v20.v19.v18.v17.v16.v15.v14.v13.v12.v11,
    v22.v20.v19.v18.v17.v16.v15.v14.v13.v12.v11.v10,
    v22.v20.v19.v18.v17.v16.v15.v14.v13.v12.v11.v10.v09,
):
    module.APP = APP
    module.GENERATED = GENERATED
    module.AUTOSAVE = AUTOSAVE
    module.LIVE_CONFIG = LIVE_CONFIG

COLORS = v22.COLORS
PANELS = v22.PANELS
PANEL_LABELS = v22.PANEL_LABELS
v22.BASE.SOURCE = FRESH_TEMPLATE
v22.v20.v19.v18.v17.v16.v15.v14.v13.v12.v11.v10.v09.BLANK_SOURCE = FRESH_BLANK


class StudioV23(v22.StudioV22):
    def __init__(self):
        self.panel_edges = {}
        self.original_mode = False
        migrate = not AUTOSAVE.exists() and V22_AUTOSAVE.exists()
        super().__init__()
        self.title('RaceCore Livery Studio v2.3 - Paneeldetails')
        if migrate:
            self.load_path(V22_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  v2.3')
        if getattr(self, 'carrosserie_menu', None):
            menu = self.carrosserie_menu[1]
            menu.insert_command(
                0, label='Originele Ferrari-basis opnieuw laden',
                command=self.restore_original_template,
            )
            menu.insert_separator(1)
            menu.insert_command(
                4, label='Paneelranden & detailaccenten',
                command=self.panel_edge_dialog,
            )

    def snapshot(self):
        state = super().snapshot()
        state['schemaVersion'] = 4
        state['panelEdgeDetails'] = copy.deepcopy(self.panel_edges)
        state['originalMode'] = self.original_mode
        return state

    def apply_state(self, state, record=False):
        state = copy.deepcopy(state)
        self.original_mode = bool(state.get('originalMode', False))
        details = {}
        for key, raw in state.get('panelEdgeDetails', {}).items():
            if key not in PANEL_LABELS:
                continue
            details[key] = {
                'colour': v22.safe_colour(raw.get('colour'), '#FFFFFF'),
                'width': max(4, min(48, int(raw.get('width', 14)))),
                'opacity': max(10, min(100, int(raw.get('opacity', 100)))),
            }
        self.panel_edges = details
        super().apply_state(state, record)

    def new_blank_project(self):
        self.panel_edges = {}
        self.original_mode = False
        super().new_blank_project()

    def restore_original_template(self):
        self.original_mode = True
        self.blank_mode = False
        self.panel_colours = {}
        self.carbon_panels = set()
        self.body_pattern = copy.deepcopy(v22.PATTERN_DEFAULTS)
        self.panel_edges = {}
        self.shape_layers = []
        self.stripe_layers = []
        self.text_layers = []
        self.logo_layers = []
        self.logo_enabled = False
        self.changed()
        self.status.set('Originele Ferrari-template opnieuw als verse basis geladen')

    def panel_edge_dialog(self):
        win = tk.Toplevel(self)
        win.title('RaceCore Paneelranden & Detailaccenten')
        win.geometry('920x650')
        win.minsize(840, 590)
        win.configure(bg=COLORS.PANEL)
        win.transient(self)
        win.grab_set()

        draft = copy.deepcopy(self.panel_edges)
        tk.Label(win, text='PANEELRANDEN', font=('Segoe UI', 16, 'bold'),
                 fg=COLORS.CYAN, bg=COLORS.PANEL).pack(anchor='w', padx=24,
                                                       pady=(20, 4))
        tk.Label(
            win,
            text=('Voeg automatisch een strakke lijn langs de rand van één of meer '
                  'carrosseriepanelen toe. Je hoeft zelf geen UV-lijn te tekenen.'),
            font=('Segoe UI', 9), fg=COLORS.MUTED, bg=COLORS.PANEL,
            wraplength=850, justify='left',
        ).pack(anchor='w', padx=24, pady=(0, 14))

        content = tk.Frame(win, bg=COLORS.PANEL)
        content.pack(fill='both', expand=True, padx=24)
        left = tk.Frame(content, bg='#08152C')
        left.pack(side='left', fill='both', expand=True, padx=(0, 12))
        right = tk.Frame(content, bg='#101F3C', width=320)
        right.pack(side='right', fill='y')
        right.pack_propagate(False)

        tree_box = tk.Frame(left, bg='#08152C')
        tree_box.pack(fill='both', expand=True, padx=12, pady=12)
        tree = ttk.Treeview(tree_box, columns=('status',), show='tree headings',
                            selectmode='browse', height=18)
        tree.heading('#0', text='Paneel')
        tree.heading('status', text='Randaccent')
        tree.column('#0', width=290)
        tree.column('status', width=130, anchor='center')
        scroll = ttk.Scrollbar(tree_box, orient='vertical', command=tree.yview)
        tree.configure(yscrollcommand=scroll.set)
        tree.pack(side='left', fill='both', expand=True)
        scroll.pack(side='right', fill='y')

        title = tk.StringVar()
        width = tk.IntVar(value=14)
        opacity = tk.IntVar(value=100)
        updating_controls = [False]
        tk.Label(right, text='GESELECTEERD PANEEL', font=('Segoe UI', 9, 'bold'),
                 fg='#93C5FD', bg='#101F3C').pack(anchor='w', padx=20,
                                                  pady=(20, 5))
        tk.Label(right, textvariable=title, font=('Segoe UI', 14, 'bold'),
                 fg=COLORS.TEXT, bg='#101F3C', wraplength=275,
                 justify='left').pack(anchor='w', padx=20)
        swatch = tk.Button(right, font=('Consolas', 10, 'bold'), relief='flat',
                           pady=12, cursor='hand2')
        swatch.pack(fill='x', padx=20, pady=(18, 8))

        def selected_key():
            selection = tree.selection()
            return selection[0] if selection else PANELS[0][0]

        def refresh_tree(select=None):
            current = select or selected_key()
            tree.delete(*tree.get_children())
            for key, label in PANELS:
                detail = draft.get(key)
                status = (f"{detail['colour']}  {detail['width']}px"
                          if detail else 'Uit')
                tree.insert('', 'end', iid=key, text=label, values=(status,))
            tree.selection_set(current)
            tree.focus(current)
            tree.see(current)
            refresh_editor()

        def refresh_editor(*_args):
            key = selected_key()
            detail = draft.get(key, {
                'colour': '#FFFFFF', 'width': 14, 'opacity': 100,
            })
            updating_controls[0] = True
            title.set(PANEL_LABELS[key])
            width.set(int(detail['width']))
            opacity.set(int(detail['opacity']))
            updating_controls[0] = False
            colour = detail['colour']
            rgb = tuple(int(colour[index:index + 2], 16) for index in (1, 3, 5))
            fg = '#000000' if sum(rgb) > 430 else '#FFFFFF'
            swatch.configure(text=f'RANDKLEUR  {colour}', bg=colour, fg=fg,
                             activebackground=colour, activeforeground=fg,
                             command=choose_colour)
            toggle.configure(
                text=('RANDACCENT AAN  —  KLIK OM UIT TE ZETTEN'
                      if key in draft else 'RANDACCENT UIT  —  KLIK OM AAN TE ZETTEN'),
                bg=('#86EFAC' if key in draft else '#334155'),
                fg=(COLORS.NAVY if key in draft else COLORS.TEXT),
                activebackground=('#BBF7D0' if key in draft else '#475569'),
            )

        def choose_colour():
            key = selected_key()
            current = draft.get(key, {}).get('colour', '#FFFFFF')
            value = colorchooser.askcolor(current, title='Kies randkleur')[1]
            if value:
                detail = draft.setdefault(key, {
                    'colour': '#FFFFFF', 'width': width.get(),
                    'opacity': opacity.get(),
                })
                detail['colour'] = value.upper()
                refresh_tree(key)

        def toggle_detail():
            key = selected_key()
            if key in draft:
                draft.pop(key)
            else:
                draft[key] = {
                    'colour': '#FFFFFF', 'width': width.get(),
                    'opacity': opacity.get(),
                }
            refresh_tree(key)

        toggle = tk.Button(right, command=toggle_detail,
                           font=('Segoe UI', 9, 'bold'), relief='flat', pady=12)
        toggle.pack(fill='x', padx=20)

        def slider(title_text, variable, low, high, suffix):
            row = tk.Frame(right, bg='#101F3C')
            row.pack(fill='x', padx=20, pady=(16, 0))
            tk.Label(row, text=title_text, font=('Segoe UI', 9), fg=COLORS.TEXT,
                     bg='#101F3C').pack(side='left')
            value_label = tk.Label(row, font=('Segoe UI', 9, 'bold'),
                                   fg=COLORS.CYAN, bg='#101F3C')
            value_label.pack(side='right')
            scale_widget = tk.Scale(
                right, from_=low, to=high, orient='horizontal', variable=variable,
                showvalue=False, bg='#101F3C', troughcolor='#253656',
                highlightthickness=0, activebackground=COLORS.CYAN,
            )
            scale_widget.pack(fill='x', padx=16)
            variable.trace_add(
                'write', lambda *_args: value_label.configure(
                    text=f'{variable.get()}{suffix}'))
            value_label.configure(text=f'{variable.get()}{suffix}')

        slider('Lijndikte', width, 4, 48, 'px')
        slider('Dekking', opacity, 10, 100, '%')

        def store_values(*_args):
            if updating_controls[0]:
                return
            key = selected_key()
            if key in draft:
                draft[key]['width'] = width.get()
                draft[key]['opacity'] = opacity.get()
                refresh_tree(key)

        width.trace_add('write', store_values)
        opacity.trace_add('write', store_values)
        tree.bind('<<TreeviewSelect>>', refresh_editor)

        actions = tk.Frame(win, bg=COLORS.PANEL)
        actions.pack(fill='x', padx=24, pady=18)

        def clear_all():
            draft.clear()
            refresh_tree()

        def apply():
            self.panel_edges = copy.deepcopy(draft)
            self.changed()
            self.status.set(f'{len(self.panel_edges)} paneelrandaccenten actief')
            win.destroy()

        tk.Button(actions, text='ALLE RANDEN UIT', command=clear_all,
                  font=('Segoe UI', 9, 'bold'), fg=COLORS.TEXT, bg='#475569',
                  relief='flat', padx=16, pady=10).pack(side='left')
        tk.Button(actions, text='TOEPASSEN', command=apply,
                  font=('Segoe UI', 10, 'bold'), fg=COLORS.NAVY, bg=COLORS.CYAN,
                  relief='flat', padx=24, pady=10).pack(side='right')
        tk.Button(actions, text='ANNULEREN', command=win.destroy,
                  font=('Segoe UI', 9, 'bold'), fg=COLORS.TEXT, bg='#334155',
                  relief='flat', padx=18, pady=10).pack(side='right', padx=8)
        refresh_tree(PANELS[0][0])

    def compose_panel_edges(self, canvas, editable):
        for key, detail in self.panel_edges.items():
            if key not in PANEL_LABELS:
                continue
            mask = ImageChops.multiply(self.panel_mask(key, canvas.size), editable)
            small = mask.resize((1024, 1024), Image.Resampling.NEAREST)
            radius = max(1, round(int(detail['width']) / 4))
            filter_size = min(25, radius * 2 + 1)
            eroded = small.filter(ImageFilter.MinFilter(filter_size))
            edge = ImageChops.subtract(small, eroded).resize(
                canvas.size, Image.Resampling.NEAREST)
            edge = ImageChops.multiply(edge, mask)
            alpha = int(255 * int(detail['opacity']) / 100)
            edge = ImageChops.multiply(edge, Image.new('L', canvas.size, alpha))
            colour = Image.new('RGBA', canvas.size,
                               (*v22.BASE.rgb(detail['colour']), 255))
            canvas = Image.composite(colour, canvas, edge)
        return canvas

    def compose_pattern(self, canvas, shade, editable):
        canvas = super().compose_pattern(canvas, shade, editable)
        return self.compose_panel_edges(canvas, editable)

    def carbon_region_map(self, size, _editable):
        """Maak carbon per gekozen paneel zonder gaten voor stickers of decals."""
        carbon = Image.new('L', size, 0)
        for key in self.carbon_panels:
            carbon = ImageChops.lighter(carbon, self.panel_mask(key, size))

        # Materiaal ligt onder nummerplaten, logo's en vaste markeringen.
        # Gebruik daarom de volledige carrosseriemasker, niet editable_mask.
        body = v22.BASE.load_l('carbon.png')
        if body.size != size:
            body = body.resize(size, Image.Resampling.NEAREST)
        carbon = ImageChops.multiply(carbon, body)
        carbon = carbon.point(lambda value: 255 if value >= 128 else 0)
        region = Image.new('RGB', size, (0, 0, 0))
        red = Image.new('RGB', size, (255, 0, 0))
        region = Image.composite(red, region, carbon)

        # LMU gebruikt wit voor logo's en nummerplaten bovenop een materiaal.
        # Zo blijft de decal leesbaar terwijl het paneel eronder volledig carbon is.
        protected = ImageChops.multiply(self.protected_mask(size), carbon)
        protected = protected.point(lambda value: 255 if value >= 128 else 0)
        region = Image.composite(Image.new('RGB', size, (255, 255, 255)),
                                 region, protected)
        return region, carbon

    def carbon_preview(self, canvas, mask):
        if not mask.getbbox():
            return canvas
        overlay = Image.new('RGBA', canvas.size, (11, 18, 28, 255))
        grid = Image.new('RGBA', canvas.size, (0, 0, 0, 0))
        draw = ImageDraw.Draw(grid)
        step = 26
        for offset in range(-canvas.size[1], canvas.size[0], step):
            draw.line((offset, 0, offset + canvas.size[1], canvas.size[1]),
                      fill=(90, 110, 126, 115), width=5)
            draw.line((offset + 10, 0,
                       offset + 10 + canvas.size[1], canvas.size[1]),
                      fill=(5, 10, 18, 150), width=5)
        woven = Image.alpha_composite(overlay, grid)
        preview = Image.composite(woven, canvas, mask)
        protected = ImageChops.multiply(self.protected_mask(canvas.size), mask)
        return Image.composite(canvas, preview, protected)

    def _worker(self, apply):
        if not self.original_mode:
            return super()._worker(apply)
        try:
            canvas = Image.open(FRESH_TEMPLATE).convert('RGBA')
            original = Image.open(FRESH_TEMPLATE).convert('RGB')
            gray = original.convert('L')
            shade = Image.merge('RGB', (gray, gray, gray))
            editable = self.editable_mask(canvas.size)

            canvas = self.compose_panel_colours(canvas, shade, editable)
            canvas = self.compose_pattern(canvas, shade, editable)
            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)
            region, carbon_mask = self.carbon_region_map(canvas.size, editable)
            region.save(GENERATED / 'customskin_region.tga',
                        format='TGA', compression=None)
            preview = self.carbon_preview(canvas, carbon_mask).convert('RGB')
            preview.thumbnail((880, 680))
            preview.save(GENERATED / 'preview.png', optimize=True)
            self.autosave()
            label = 'Originele Ferrari-template actief'
            if apply:
                result = subprocess.run(
                    [sys.executable, str(v22.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 = ('Originele basis live in LMU'
                         if event['status'] == 'installed'
                         else 'LMU gebruikt de originele basis al')
            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__':
    StudioV23().mainloop()
