import copy
import io
import os
import sys
import json
from tkinter import simpledialog
import uuid
import ast
import datetime
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
from xml.dom.minidom import parseString
from PIL import Image
import base64
import cairosvg
from dotenv import load_dotenv
import warnings
import threading
import numpy as np
import svgwrite
import xml.etree.ElementTree as ET

warnings.filterwarnings("ignore", category=UserWarning, module="PIL.PngImagePlugin")

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils

from sketch_canvas import SketchCanvas
from chat_panel import ChatPanel
from stroke_panel import StrokePanel,LayoutPanel
from llm_handler import LLMHandler
from prompts.interactive_prompts import get_system_prompt
from simplify_spline import generate_cubicspline

class InteractiveSketchApp:
    def __init__(self, root):
        self.root = root
        self.root.title("CoSketcher@2025")
        self.root.geometry("1400x950")
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        self.res = 600
        self.cell_size = 1
        self.stroke_width = 2
        self.grid_size = self.res + 2
        
        self.init_canvas = None
        
        self.llm_handler = LLMHandler()
        self.session_id = str(uuid.uuid4())
        self.response_dir = "response_records"
        os.makedirs(self.response_dir, exist_ok=True)
        self.session_dir = os.path.join(self.response_dir, f"session_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}")
        os.makedirs(self.session_dir, exist_ok=True)
        
        self.strokes = []
        self.stroke_counter = 0
        self.highlighted_stroke_index = -1
        self.layouts = []
        self.editing_layout = {}
        self.layouts_before = []
        
        self.edit_mode = tk.StringVar(value="add_only")
        self.skeleton_visible = True
        self.layout_visible = True
        
        self.current_selection = None
        self.current_grid_selection = None
        
        self.message_history = []
        self.input_image = False
        
        self.temperature = tk.DoubleVar(value=0.7)

        white_image = Image.new("RGB", (self.res, self.res), (255, 255, 255))
        white_image.save("./prompts/examples/current_wibg.png")
        
        self.setup_ui()
        
    def setup_ui(self):
        style = ttk.Style()
        style.configure("Rounded.TButton", padding=6, relief="flat", background="#f0f0f0")
        style.map("Rounded.TButton", background=[('active', '#d9d9d9')], relief=[('pressed', 'sunken')])

        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.left_width = 200
        left_frame = ttk.Frame(main_frame, width=self.left_width)
        left_frame.pack(side=tk.LEFT, fill=tk.Y)
        left_frame.pack_propagate(False)

        resize_grip = ttk.Frame(main_frame, width=5, cursor='sb_h_double_arrow')
        resize_grip.pack(side=tk.LEFT, fill=tk.Y)

        center_frame = ttk.Frame(main_frame, width=750)
        center_frame.pack(side=tk.LEFT, fill=tk.Y)
        center_frame.pack_propagate(False)

        right_frame = ttk.Frame(main_frame)
        right_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        stroke_panel_frame = ttk.Frame(left_frame)
        stroke_panel_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        self.stroke_panel = StrokePanel(stroke_panel_frame, highlight_callback=self.highlight_stroke_by_id)
        self.stroke_panel.pack(fill=tk.BOTH, expand=True)
        self.stroke_panel.pack_propagate(False)

        layout_panel_frame = ttk.Frame(left_frame)
        layout_panel_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)

        self.layout_panel = LayoutPanel(layout_panel_frame, highlight_callback=self.highlight_layout_by_id)
        self.layout_panel.pack(fill=tk.BOTH, expand=True)
        self.layout_panel.pack_propagate(False)

        canvas_container = ttk.Frame(center_frame)
        canvas_container.pack(side=tk.TOP, padx=10, pady=10)
        canvas_container.config(borderwidth=2, relief="sunken")

        self.sketch_canvas = SketchCanvas(self, canvas_container, self.grid_size, self.res + 2, self.cell_size)
        self.sketch_canvas.pack(padx=5, pady=5)

        self.sketch_canvas.set_stroke_callback(self.on_stroke_completed)
        self.sketch_canvas.set_edit_callback(self.on_stroke_edited)
        self.sketch_canvas.set_selection_callback(self.on_selection_complete)
        self.sketch_canvas.set_status_callback(self.on_status_show)

        control_frame = ttk.Frame(center_frame)
        control_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(0, 5))

        search_frame1 = ttk.Frame(control_frame)
        search_frame1.pack(fill=tk.X, padx=5, pady=(0, 5))

        ttk.Button(search_frame1, text="Invert Y-axis", command=self.invert_Yvalue, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        self.show_or_hide_sk_btn = ttk.Button(search_frame1, text="Show Skeleton", command=self.show_or_hide_skeleton, style="Rounded.TButton")
        self.show_or_hide_sk_btn.pack(side=tk.LEFT, padx=(0, 5))
        self.show_or_hide_ly_btn = ttk.Button(search_frame1, text="Show Layout", command=self.show_or_hide_layout, style="Rounded.TButton")
        self.show_or_hide_ly_btn.pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame1, text="Clear Highlight", command=self.clear_highlight, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame1, text="Clear Canvas", command=self.clear_canvas, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))

        search_frame2 = ttk.Frame(control_frame)
        search_frame2.pack(fill=tk.X, padx=5, pady=(0, 5))
        ttk.Button(search_frame2, text="Delete Selected Stroke", command=self.delete_highlighted_stroke, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame2, text="Edit Selected Stroke", command=self.edit_highlighted_stroke, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame2, text="Edit Corresponding Skeleton", command=self.edit_highlighted_skeleton, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame2, text="Edit Selected Layout", command=self.edit_highlighted_layout, style="Rounded.TButton").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(search_frame2, text="Exit Edit Mode", command=self.exit_edit_mode, style="Rounded.TButton").pack(side=tk.LEFT)

        temp_frame = ttk.LabelFrame(control_frame, text="Model Temperature Control")
        temp_frame.pack(fill=tk.X, padx=5, pady=(5, 0))

        temp_slider_frame = ttk.Frame(temp_frame)
        temp_slider_frame.pack(fill=tk.X, padx=5, pady=5)

        ttk.Label(temp_slider_frame, text="Low (Deterministic)").pack(side=tk.LEFT)
        self.temp_slider = ttk.Scale(temp_slider_frame, from_=0.0, to=1.0, orient=tk.HORIZONTAL,
                                    variable=self.temperature, length=200)
        self.temp_slider.pack(side=tk.LEFT, padx=10, fill=tk.X, expand=True)
        ttk.Label(temp_slider_frame, text="High (Creative)").pack(side=tk.LEFT)

        temp_value_frame = ttk.Frame(temp_frame)
        temp_value_frame.pack(fill=tk.X, padx=5, pady=(0, 5))

        ttk.Label(temp_value_frame, text="Current Temperature:").pack(side=tk.LEFT)
        self.temp_value_label = ttk.Label(temp_value_frame, text=f"{self.temperature.get():.2f}")
        self.temp_value_label.pack(side=tk.LEFT, padx=5)
        self.temperature.trace_add("write", self.update_temp_label)

        canvas_controls = ttk.Frame(control_frame)
        canvas_controls.pack(fill=tk.X, padx=5, pady=5)

        ttk.Button(canvas_controls, text="Save Image", command=self.save_image, style="Rounded.TButton").pack(side=tk.LEFT, padx=5)
        ttk.Button(canvas_controls, text="Undo", command=self.undo_stroke, style="Rounded.TButton").pack(side=tk.LEFT, padx=5)
        ttk.Button(canvas_controls, text="Select Region", command=self.start_selection_mode, style="Rounded.TButton").pack(side=tk.LEFT, padx=5)
        ttk.Button(canvas_controls, text="Clear Selection", command=self.clear_selection, style="Rounded.TButton").pack(side=tk.LEFT, padx=5)

        mode_frame = ttk.LabelFrame(canvas_controls, text="Drawing Mode")
        mode_frame.pack(side=tk.RIGHT, padx=5)
        ttk.Radiobutton(mode_frame, text="Add Only", variable=self.edit_mode, value="add_only").pack(side=tk.LEFT, padx=5)
        ttk.Radiobutton(mode_frame, text="Editable", variable=self.edit_mode, value="edit_all").pack(side=tk.LEFT, padx=5)

        self.chat_panel = ChatPanel(right_frame, self.send_message)
        self.chat_panel.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.strokes_text = scrolledtext.ScrolledText(right_frame, wrap=tk.WORD, width=60)
        self.strokes_text.pack(fill=tk.BOTH, expand=False, padx=5, pady=5)
        self.strokes_text.bind("<FocusOut>", self.on_strokes_text_modified)

        self.status_var = tk.StringVar(value="Ready")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        self.bind_resize_grip(resize_grip, left_frame)


    def bind_resize_grip(self, grip_frame, target_frame):
        def start_resize(event):
            self._resizing = True
            self._start_x = event.x_root
            self._start_width = self.left_width

        def do_resize(event):
            if not getattr(self, '_resizing', False):
                return
            delta = event.x_root - self._start_x
            new_width = max(100, self._start_width + delta)
            self.left_width = new_width
            target_frame.config(width=self.left_width)

        def stop_resize(event):
            self._resizing = False

        grip_frame.bind("<Button-1>", start_resize)
        grip_frame.bind("<B1-Motion>", do_resize)
        grip_frame.bind("<ButtonRelease-1>", stop_resize)


    def on_strokes_text_modified(self, event=None):
        import re
        try:
            raw_text = self.strokes_text.get("1.0", tk.END)
            if "<strokes>" not in raw_text:
                self.status_var.set("No <strokes> tag detected")
                return
            root = ET.fromstring(f"<root>{raw_text}</root>")
            strokes_root = root.find("strokes")
            layout_root = root.find("layout")

            new_strokes = []
            for stroke_elem in strokes_root:
                stroke_id = stroke_elem.tag
                skeleton_elem = stroke_elem.find("skeleton")
                points_elem = stroke_elem.find("points")
                id_elem = stroke_elem.find("id")

                if points_elem is not None:
                    skeleton_text = skeleton_elem.text or ""
                    points_text = points_elem.text or ""
                    skeleton = re.findall(r"x\d+y\d+", skeleton_text)
                    points = re.findall(r"x\d+y\d+", points_text)
                    stroke = {
                        "skeleton": skeleton,
                        "points": points,
                        "id": id_elem.text.strip() if id_elem is not None else stroke_id
                    }
                    new_strokes.append(stroke)
            layout_list = []
            for layout_elem in layout_root:
                layout_name_elem = layout_elem.find("meaning")
                layout_name = layout_name_elem.text or ""
                layout_strokes = layout_elem.find("s_group")
                layout_rec = layout_elem.find("rec")

                stroke_index = []
                if layout_strokes is not None and layout_rec is not None:
                    strokename_text = layout_strokes.text or ""
                    strokename = re.findall(r"s\d+",strokename_text)
                    for sn in strokename:
                        stroke_index.append(int(sn[1:]))
                        
                    rec_text = layout_rec.text or ""
                    rec_xy = re.findall(r"x\d+y\d+",rec_text)
                    rec_xyxy = []
                    for r in rec_xy:
                        xy_list = r.split("y")
                        rec_xyxy.append(xy_list[0][1:])
                        rec_xyxy.append(xy_list[1])
                    layout = {
                        "meaning":layout_name,
                        "s_group":stroke_index,
                        "rec":rec_xyxy
                    }
                    layout_list.append(layout)
            self.strokes = new_strokes
            self.stroke_counter = len(new_strokes)
            self.layouts = layout_list
            self.redraw_canvas()
            self.update_stroke_panel()
            self.update_layout_panel()
            self.update_strokes_text()
            self.status_var.set("Canvas updated from text box (XML schema)")

        except Exception as e:
            import traceback
            traceback.print_exc()
            self.status_var.set(f"Error in parsing XML: {str(e)}")
    
    def update_strokes_text(self):
        try:
            content = "<strokes>\n"
            for i, stroke in enumerate(self.strokes, 1):
                stroke_id = f"s{i}"
                points = stroke.get("points", [])
                skeleton = stroke.get("skeleton", [])
                id = stroke.get("id",f"stroke_{i}")

                content += f"  <{stroke_id}>\n"
                content += "    <skeleton>"
                content += ", ".join([f"'{p}'" for p in skeleton])
                content += "</skeleton>\n"
                content += "    <points>"
                content += ", ".join([f"'{p}'" for p in points])
                content += "</points>\n"

                content += f"    <id>{id}</id>\n"
                content += f"  </{stroke_id}>\n"
            content += "</strokes>"

            content += "\n<layout>\n"
            if hasattr(self,"layouts"):
                for i,layout in enumerate(self.layouts,1):
                    layout_name = layout.get("meaning", "")
                    stroke_indices = layout.get("s_group", [])
                    rec = layout.get("rec", [])

                    content += f"  <l{i}>\n"
                    content += f"    <meaning>{layout_name}</meaning>\n"
                    content += "    <s_group>"
                    content += ", ".join([f"s{i}" for i in stroke_indices])
                    content += "</s_group>\n"
                    content += "    <rec>"
                    rec_pairs = [f"x{rec[i]}y{rec[i+1]}" for i in range(0, len(rec), 2)]
                    content += ", ".join([f"'{p}'" for p in rec_pairs])
                    content += "</rec>\n"
                    content += f"  </l{i}>\n"
            content += "</layout>"

            self.strokes_text.delete("1.0", tk.END)
            self.strokes_text.insert(tk.END, content)

        except Exception as e:
            print(f"Error: {e}")
    
    def invert_Yvalue(self):
        for i,stroke in enumerate(self.strokes):
            new_skeleton = []
            for point in stroke.get('skeleton', []):
                try:
                    x, y = self.parse_point(point)
                    new_skeleton.append(f"x{x}y{self.res - y + 1}")
                except Exception as e:
                    print(f"Error in parsing point {point}: {e}")
            self.strokes[i]['skeleton'] = new_skeleton

            new_points = []
            for point in stroke.get('points', []):
                try:
                    x, y = self.parse_point(point)
                    new_points.append(f"x{x}y{self.res - y + 1}")
                except Exception as e:
                    print(f"Error in parsing point {point}: {e}")
            self.strokes[i]['points'] = new_points

        self.redraw_canvas()
        self.update_strokes_text()
        self.status_var.set("Flipped y-axis direction")

    def show_or_hide_skeleton(self):
        self.skeleton_visible = not getattr(self, "skeleton_visible", True)
        if self.skeleton_visible:
            self.show_or_hide_sk_btn.config(text="Hide Skeleton")
            self.sketch_canvas.show_skeletons(True)
        else:
            self.show_or_hide_sk_btn.config(text="Show Skeleton")
            self.sketch_canvas.show_skeletons(False)
    
    def show_or_hide_layout(self):
        self.layout_visible = not getattr(self, "layout_visible", True)
        if self.layout_visible:
            self.show_or_hide_ly_btn.config(text="Hide Skeleton")
            self.sketch_canvas.show_layouts(True)
        else:
            self.show_or_hide_ly_btn.config(text="Show Skeleton")
            self.sketch_canvas.show_layouts(False)
    
    def search_stroke_by_id(self):
        search_term = self.id_search_var.get().strip()
        if not search_term:
            messagebox.showinfo("tip", "Please enter the line ID or number to be found")
            return
        found = False
        if search_term.isdigit():
            index = int(search_term) - 1 
            if 0 <= index < len(self.strokes):
                stroke = self.strokes[index]
                stroke_id = stroke.get('id', '')
                self.status_var.set(f"Line found: #{index+1} (ID: {stroke_id})")
                self.highlight_stroke(index, stroke_id)
                found = True
            else:
                self.status_var.set(f"Index {search_term} out of range")
                return
        else:
            for i, stroke in enumerate(self.strokes):
                stroke_id = stroke.get('id', '')
                
                if search_term.lower() in stroke_id.lower():
                    self.status_var.set(f"Line found: {stroke_id} (index: {i+1})")
                    
                    self.highlight_stroke(i, stroke_id)
                    found = True
                    break
                
                if search_term.lower() == f"s{i+1}":
                    self.status_var.set(f"Line found: s{i+1} (ID: {stroke_id})")
                    
                    self.highlight_stroke(i, stroke_id)
                    found = True
                    break
        
        if not found:
            self.status_var.set(f"No line found with ID or number '{search_term}'")
            messagebox.showinfo("tip", f"No line found with ID or number '{search_term}'")
    
    def highlight_stroke(self, index, stroke_id):
        if self.sketch_canvas.highlighted_id == stroke_id:
            self.sketch_canvas.highlight_stroke(None)
            self.highlighted_stroke_index = -1
            return

        self.sketch_canvas.highlight_stroke(index)
        self.sketch_canvas.highlighted_id = stroke_id
        self.highlighted_stroke_index = index
    
    def clear_highlight(self):
        if self.sketch_canvas.edit_mode:
            self.exit_edit_mode()
        if self.highlighted_stroke_index != -1:
            self.sketch_canvas.highlight_stroke(None)
            self.highlighted_stroke_index = -1
        elif self.sketch_canvas.highlighted_layout_id != -1:
            self.sketch_canvas.highlight_layout(None)
            self.sketch_canvas.highlighted_layout_id = -1  
        self.status_var.set("Highlighted cleared")
    
    def highlight_layout(self, index):
        if self.sketch_canvas.highlighted_layout_id == index:
            self.sketch_canvas.highlight_layout(None)
            self.sketch_canvas.highlighted_layout_id = -1
            return

        self.sketch_canvas.highlight_layout(index)
    
    def clear_canvas(self):
        if messagebox.askyesno("Confirm", "Are you sure you want to clear the canvas?"):
            self.sketch_canvas.clear()
            self.strokes = []
            self.layouts = []
            self.stroke_counter = 0
            self.status_var.set("Canvas cleared")
            self.redraw_canvas()
            self.update_strokes_text()

    
    def undo_stroke(self):
        if self.strokes:
            self.strokes.pop()
            self.stroke_counter -= 1
            self.redraw_canvas()
            self.status_var.set("Last transaction was revoked")
            self.update_strokes_text()

    
    def redraw_canvas(self):
        self.sketch_canvas.clear()
        self.sketch_canvas.init_canvas()
        for i,stroke in enumerate(self.strokes):
            skeleton = stroke.get('skeleton', [])
            points = stroke.get('points', [])
            
            pixel_skeleton = []
            for sk_point in skeleton:
                try:
                    x, y = self.parse_point(sk_point)
                    pixel_skeleton.append((x,y))
                except Exception as e:
                    print(f"Error in parsing point {sk_point}: {e}")
            if len(pixel_skeleton) >= 2:
                spline = generate_cubicspline(pixel_skeleton)
                draw_points = [(x,y) for x,y in spline]
                self.sketch_canvas.draw_current_stroke(draw_points, skeleton_index=i)
            pixel_points = []
            for point in points:
                try:
                    x, y = self.parse_point(point)
                    pixel_points.append((x, y))
                except Exception as e:
                    print(f"Error in parsing point {point}: {e}")
            
            if pixel_points:
                spline = generate_cubicspline(pixel_points)
                draw_points = [(x, y) for x, y in spline]
                self.sketch_canvas.draw_current_stroke(draw_points, stroke_index=i)
        
        for i,layout in enumerate(self.layouts):
            rec = layout.get('rec','')
            if len(rec) == 4:
                self.sketch_canvas.draw_layout_rec(rec, layout_index=i)

        png_content = self.generate_res(is_svg=False,auto_save=True)
        with open("./prompts/examples/current_wibg.png", 'wb') as f:
            f.write(png_content.read())

        self.update_stroke_panel()
        self.update_layout_panel()
        self.sketch_canvas.show_skeletons(self.skeleton_visible)
        self.sketch_canvas.show_layouts(self.layout_visible)
    
    def parse_point(self, point_str):
        try:
            point_str = point_str.strip().strip("'\"")
            if 'x' in point_str and 'y' in point_str:
                x_part = point_str.split('y')[0]
                y_part = point_str.split('y')[1]
                
                x = int(x_part[1:])
                y = int(y_part)
                return (x,y)
            else:
                parts = point_str.replace('(', '').replace(')', '').split(',')
                if len(parts) >= 2:
                    x = int(parts[0])
                    y = int(parts[1])
                    return (x,y)
                else:
                    raise ValueError(f"Cannot resolve point coordinates: {point_str}")
        except Exception as e:
            print(f"Error in parsing point {point_str}: {e}")
            return None, None
    
    def save_image(self):
        file_path = filedialog.asksaveasfilename(
            defaultextension=".svg",
            filetypes=[("PNG files", "*.png"), ("SVG files", "*.svg"), ("All files", "*.*")],
            initialdir=self.session_dir
        )
        
        if file_path.endswith('.svg'):
            svg_content = self.generate_res(is_svg=True)
            with open(file_path, 'w') as f:
                f.write(svg_content)
        elif file_path.endswith('.png'):
            png_content = self.generate_res(is_svg=False)
            with open(file_path, 'wb') as f:
                f.write(png_content.read())
            
        self.status_var.set(f"Image saved to {file_path}")
    
    def generate_res(self, is_svg=True,auto_save=False):
        strokes_all = []
        
        for stroke in self.strokes:
            points = stroke.get('points', [])
            cleaned_points = [p.strip().strip("'\"") for p in points]
            strokes_all.append(cleaned_points)

        all_control_points = []
        for points in strokes_all:
            pixel_points = []
            for point in points:
                try:
                    x, y = self.parse_point(point)
                    pixel_points.append((x, y))
                except Exception as e:
                    print(f"Error in parsing point {point}: {e}")
            cubicspline_points = list(generate_cubicspline(pixel_points))
            result = cubicspline_points[::2]
            if (result[-1] != cubicspline_points[-1]).any():
                result.append(cubicspline_points[-1])
            all_control_points.append(np.array(result))

        dwg = svgwrite.Drawing(size=(self.sketch_canvas.size, self.sketch_canvas.size))
        for points in all_control_points:
            if len(points) >= 2:
                path_data = f"M {points[0][0]} {points[0][1]}"
                for x, y in points[1:]:
                    path_data += f" L {x} {y}"
                dwg.add(dwg.path(d=path_data, stroke="black", fill="none", stroke_width=int(self.stroke_width/2)))

        raw_svg = dwg.tostring()
        if is_svg==True:
            pretty_svg = parseString(raw_svg).toprettyxml(indent="  ")
            return '\n'.join(pretty_svg.splitlines()[1:]) 
        else:
            png_output = io.BytesIO()
            cairosvg.svg2png(
                bytestring=raw_svg.encode('utf-8'),
                write_to=png_output,
                background_color='white'
            )
            png_output.seek(0)
            return png_output
    
    def parse_stroke_from_canvas(self, skeleton_data, stroke_data,stroke_name=None):
        cur_user_input_stroke = f"\t\t<skeleton>"
        cur_skeleton = []
        for point_data in skeleton_data:
            x, y = point_data['x'], point_data['y']
            x = min(600, max(1, x))
            y = min(600, max(1, y))
            point_str = f'x{x}y{y}'
            if (not cur_skeleton) or (cur_skeleton[-1] != point_str):
                cur_skeleton.append(point_str)
                cur_user_input_stroke += f"'{point_str}', "
        cur_user_input_stroke = cur_user_input_stroke[:-2]

        cur_user_input_stroke += f"</skeleton>\n\t\t<points>"
        cur_stroke = []
        for point_data in stroke_data:
            x, y = point_data['x'], point_data['y']
            x = min(600, max(1, x))
            y = min(600, max(1, y))
            point_str = f'x{x}y{y}'
            if (not cur_stroke) or (cur_stroke[-1] != point_str):
                cur_stroke.append(point_str)
                cur_user_input_stroke += f"'{point_str}', "
        
        cur_user_input_stroke = cur_user_input_stroke[:-2]
        cur_user_input_stroke += f"</points>\n\t\t<id>{stroke_name}</id>"
        return cur_user_input_stroke

    
    def send_message(self, message):
        self.chat_panel.add_message("user", message)
        self.status_var.set("Communicating with LLM...")
        
        current_sketch = "<strokes>\n"
        for i, stroke in enumerate(self.strokes, 1):
            self.stroke_counter = i
            sk_data = []
            for sk in stroke.get('skeleton',[]):
                x, y = self.parse_point(sk)
                sk_data.append({'x':x, 'y': y})
            sim_data = []
            for pt in stroke.get('points', []):
                x, y = self.parse_point(pt)
                sim_data.append({'x': x, 'y': y})
            stroke_name = stroke.get('id',"")
            current_sketch += f"\t<s{i}>\n" + self.parse_stroke_from_canvas(sk_data,sim_data,stroke_name) + f"</s{i}>"
        current_sketch += "</strokes>\n"

        current_sketch = "<layout>\n"
        if hasattr(self,"layouts"):
            for i, layout in enumerate(self.layouts, 1):
                layout_name = layout.get("meaning", "")
                stroke_indices = layout.get("s_group", [])
                rec = layout.get("rec", [])

                current_sketch += f"\t<l{i}>\n"
                current_sketch += f"\t\t<meaning>{layout_name}</meaning>\n"
                current_sketch += " \t\t<s_group>"
                current_sketch += ", ".join([f"s{i}" for i in stroke_indices])
                current_sketch += "</s_group>\n"
                current_sketch += "\t\t<rec>"
                rec_pairs = [f"x{rec[i]}y{rec[i+1]}" for i in range(0, len(rec), 2)]
                current_sketch += ", ".join([f"'{p}'" for p in rec_pairs])
                current_sketch += "</rec>\n"
                current_sketch += f"\t</l{i}>\n"
        current_sketch += "</strokes>"
        
        self.system_prompt = get_system_prompt(edit_mode=self.edit_mode.get())
        
        user_message = f"{message}\n\ncurrent canvas:\n{current_sketch}"
        
        if self.current_grid_selection:
            x1, y1, x2, y2 = self.current_grid_selection
            user_message += f"\n\nThe user selected the area: upper left corner ({x1},{y1}) lower right corner ({x2},{y2})"

        def encode_image(image_path):
            with open(image_path, "rb") as image_file:
                return base64.b64encode(image_file.read()).decode('utf-8')

        if self.input_image:
            image_base64 = encode_image("./prompts/examples/current_wibg.png")
            content_list = [
                    {
                        "type":"image",
                        "source":{
                            "type": "base64",
                            "media_type": "image/png",
                            "data": image_base64,
                        }
                    },
                    {
                        "type": "text",
                        "text": user_message
                    }
                ]
        else:
            content_list = user_message

        self.message_history.append({"role": "user", "content": content_list})
        
        threading.Thread(target=self.process_llm_request, args=(), daemon=True).start()
    
    def process_llm_request(self):
        try:
            self.root.after(0, lambda: self.status_var.set("Communication with LLM...please wait"))
            
            response = self.llm_handler.call_llm(
                system_prompt=self.system_prompt,
                messages=self.message_history,
                max_tokens=8000, 
                model=None
            )
            
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            response_path = os.path.join(self.session_dir, f"response_{timestamp}.json")
            
            self.root.after(0, lambda: self.handle_llm_response(response, response_path))
            
        except ValueError as e:
            error_msg = f"API Error: {str(e)}\nPlease check your API key and permissions."
            self.root.after(0, lambda: self.handle_llm_error(error_msg))
        except ConnectionError as e:
            error_msg = f"Connection error: {str(e)}\nPlease check your network connection and API key."
            self.root.after(0, lambda: self.handle_llm_error(error_msg))
        except Exception as e:
            error_msg = f"LLM communication error: {str(e)}"
            self.root.after(0, lambda: self.handle_llm_error(error_msg))
    
    def handle_llm_response(self, response, response_path):
        try:
            with open(response_path, 'w', encoding='utf-8') as f:
                json.dump({
                    "messages": self.message_history,
                    "response": response.model_dump() if hasattr(response, 'model_dump') else str(response)
                }, f, indent=2, ensure_ascii=False)
            
            response_content = response.content
            if isinstance(response_content, list):
                response_content = response_content[0].text
            
            self.message_history.append({"role": "assistant", "content": response_content})
            
            self.chat_panel.add_message("assistant", response_content)
            
            self.parse_llm_response(response_content)
            
            self.status_var.set("LLM response processed")
        except Exception as e:
            self.handle_llm_error(f"Error processing LLM response: {str(e)}")
    
    def handle_llm_error(self, error_msg):
        self.status_var.set(f"Error: {error_msg}")
        self.chat_panel.add_system_message(f"Error: {error_msg}")
        messagebox.showerror("LLM communication error", error_msg)
    
    def parse_llm_response(self, response):
        import re
        def parse_point_list(text):
            if text is None:
                return []
            text = text.strip()
            if '[' in text and ']' in text:
                return re.findall(r"['\"]?(x\d+y\d+)['\"]?", text)
            else:
                return [p.strip().strip("'\"") for p in text.split(',') if p.strip()]
        try:
            if self.edit_mode.get() == "edit_skeleton":
                stroke_start = response.find("<update>")
                if stroke_start == -1:
                    messagebox.showinfo("Hint", "No stroke data is included in the LLM response")
                    return
                
                stroke_end = response.find("</update>", stroke_start)
                if stroke_end == -1:
                    messagebox.showinfo("Hint", "The stroke data in the LLM response is incomplete")
                    return
                
                stroke_content = response[stroke_start:stroke_end + len("</update>")]

                try:
                    import xml.etree.ElementTree as ET
                    
                    root = ET.fromstring(f"<root>{stroke_content}</root>")
                    update_elem = root.find("update")
                    
                    for child in update_elem:
                        if child.tag.startswith("s"):
                            stroke_node = child
                            target_index = int(child.tag[1:])
                            break
                    
                    skeleton_elem = stroke_node.find("skeleton")
                    points_elem = stroke_node.find("points")
                    id_elem = stroke_node.find("id")

                    if skeleton_elem is None or points_elem is None or id_elem is None:
                        messagebox.showinfo("tip", "Stroke data missing skeleton/points/id tag")
                        return

                    skeleton = parse_point_list(skeleton_elem.text if skeleton_elem is not None else "")
                    points = parse_point_list(points_elem.text if points_elem is not None else "")
                    stroke_id = id_elem.text.strip() if id_elem is not None and id_elem.text else ""

                    self.strokes[target_index-1] = {
                        "skeleton": skeleton,
                        "points": points,
                        "id": stroke_id
                    }

                    pixel_points = []
                    for point in points:
                        try:
                            x, y = self.parse_point(point)
                            pixel_points.append((x, y))
                        except Exception as e:
                            print(f"Error in parsing point {point} on stroke {si} : {e}")
                    
                    if len(pixel_points) >= 2: 
                        spline = generate_cubicspline(pixel_points)
                        draw_points = [(x, y) for x, y in spline]
                        self.sketch_canvas.draw_current_stroke(draw_points, stroke_index=len(self.strokes)-1)

                    self.update_strokes_text()
                    self.on_strokes_text_modified()
                    self.update_stroke_panel()
                    self.update_layout_panel()
                    
                except Exception as e:
                    print(f"Error in parsing XML: {e}")


            else:
                strokes_start = response.find("<strokes>")
                if strokes_start == -1:
                    messagebox.showinfo("Hint", "No stroke data is included in the LLM response")
                    return
                
                strokes_end = response.find("</strokes>", strokes_start)
                if strokes_end == -1:
                    messagebox.showinfo("Hint", "The stroke data in the LLM response is incomplete")
                    return
                
                strokes_content = response[strokes_start:strokes_end + len("</strokes>")]
                
                layout_start = response.find("<layout>")
                if layout_start == -1:
                    messagebox.showinfo("Hint", "The layout data is not included in the LLM response")
                    return
                
                layout_end = response.find("</layout>", layout_start)
                if layout_end == -1:
                    messagebox.showinfo("Hint", "The layout data in the LLM response is incomplete")
                    return
                
                layout_content = response[layout_start:layout_end + len("</layout>")]
            
                try:
                    import xml.etree.ElementTree as ET
                    
                    root1 = ET.fromstring(f"<root>{strokes_content}</root>")
                    strokes_element = root1.find("strokes")
                    root2 = ET.fromstring(f"<root>{layout_content}</root>")
                    layouts_element = root2.find("layout")
                    
                    if strokes_element is None:
                        messagebox.showinfo("Prompt", "Cannot parse stroke data")
                        return
                    
                    if self.edit_mode.get() == "edit_all" or self.edit_mode.get() == "edit_layout":
                        self.strokes = []
                        self.layouts = []
                        self.stroke_counter = 0
                        self.sketch_canvas.clear()
                    
                    stroke_count = 0
                    for si, stroke_elem in enumerate(strokes_element):
                        skeleton_elem = stroke_elem.find("skeleton")
                        points_elem = stroke_elem.find("points")
                        id_elem = stroke_elem.find("id")
                        
                        if points_elem is not None:
                            skeleton = parse_point_list(skeleton_elem.text if skeleton_elem is not None else "")
                            points = parse_point_list(points_elem.text if points_elem is not None else "")
                            stroke_name = id_elem.text.strip() if id_elem is not None and id_elem.text else ""
                            
                            self.strokes.append({
                                'skeleton': skeleton,
                                'points': points,
                                'id': stroke_name
                            })
                    
                    for li, layout_elem in enumerate(layouts_element):
                        layout_name_elem = layout_elem.find("meaning")
                        layout_name = layout_name_elem.text or ""
                        layout_sgroup = layout_elem.find("s_group")
                        layout_rec = layout_elem.find("rec")

                        stroke_index = []
                        if layout_sgroup is not None and layout_rec is not None:
                            strokename_text = layout_sgroup.text or ""
                            strokename = re.findall(r"s\d+",strokename_text)
                            for sn in strokename:
                                stroke_index.append(int(sn[1:]))
                                
                            rec_text = layout_rec.text or ""
                            rec_xy = re.findall(r"x\d+y\d+",rec_text)
                            rec_xyxy = []
                            for r in rec_xy:
                                xy_list = r.split("y")
                                rec_xyxy.append(xy_list[0][1:])
                                rec_xyxy.append(xy_list[1])
                            layout = {
                                "meaning":layout_name,
                                "s_group":stroke_index,
                                "rec":rec_xyxy
                            }
                            self.layouts.append(layout)
                    self.stroke_counter = len(self.strokes)
                    self.status_var.set(f"Added {stroke_count} strokes from the LLM response")
                    
                    self.redraw_canvas()
                    self.update_strokes_text()
                    
                    if self.current_selection is not None:
                        self.clear_selection()
                except Exception as e:
                    print(f"Error in parsing XML: {e}")
        
        except Exception as e:
            print(f"Error in processing LLM responde: {e}")
    
    def update_stroke_panel(self):
        stroke_ids = list(range(1, len(self.strokes) + 1))
        stroke_names = []
        for stroke in self.strokes:
            stroke_name = stroke.get('id', '')
            if not stroke_name:
                stroke_name = f"stroke {len(stroke_names) + 1}"
            stroke_names.append(stroke_name)
        
        self.stroke_panel.update_strokes(stroke_ids, stroke_names)
    
    def update_layout_panel(self):
        if not hasattr(self,"layouts"):
            return
        layout_ids = list(range(1, len(self.layouts) + 1))
        layout_names = []
        for layout in self.layouts:
            layout_name = layout.get('meaning', '')
            if not layout_name:
                layout_name = f"stroke {len(layout_names) + 1}"
            layout_names.append(layout_name)
        
        self.layout_panel.update_layouts(layout_ids, layout_names)
    
    def on_stroke_completed(self, points, stroke_id=-1, is_skeleton=False):
        grid_points = []
        for x, y in points:
            grid_point = f"x{int(x)}y{int(y)}"
            grid_points.append(f"{grid_point}")
        
        if stroke_id >= 0:
            if is_skeleton:
                sk_points = grid_points
                st_points = self.strokes[stroke_id]['points']
            else:
                sk_points = self.strokes[stroke_id]['skeleton']
                st_points = grid_points
            stroke_name = self.strokes[stroke_id]['id']
            if stroke_name is None:
                stroke_name = f"stroke_{stroke_id + 1}" 
            stroke = {
                'skeleton': sk_points,
                'points': st_points,
                'id': stroke_name
            }
            self.strokes[stroke_id] = stroke
            self.status_var.set(f"Modified stroke #{stroke_id}")
        else:
            stroke = {
                'skeleton': [],
                'points': grid_points,
                'id': f'stroke_{len(self.strokes) + 1}'  
            }
            self.strokes.append(stroke)
            self.stroke_counter = len(self.strokes)  
            self.status_var.set(f"Added stroke #{self.stroke_counter}")
        
        self.update_stroke_panel()
        self.update_layout_panel()
        self.update_strokes_text()
        self.sketch_canvas.show_skeletons(self.skeleton_visible)
        
    
    def on_closing(self):
        if messagebox.askyesno("Confirm", "Are you sure you want to exit?"):
            self.root.destroy()

    def delete_highlighted_stroke(self):
        if self.sketch_canvas.highlighted_id is None:
            messagebox.showinfo("Tip", "Please select the line to delete first")
            return
        
        highlighted_index = None
        for i, stroke in enumerate(self.strokes):
            if stroke.get('id', '') == self.sketch_canvas.highlighted_id:
                highlighted_index = i
                break
        
        if highlighted_index is not None:
            if messagebox.askyesno("Confirm", f"Are you sure you want to delete the line {self.sketch_canvas.highlighted_id}?"):
                self.strokes.pop(highlighted_index)
                self.clear_highlight()
                self.redraw_canvas()
                self.update_stroke_panel()
                self.update_layout_panel()
                self.update_strokes_text()
                self.status_var.set(f"Deleted line {self.sketch_canvas.highlighted_id}")
        else:
            messagebox.showinfo("tip", "The line to be deleted cannot be found")
    
    def edit_highlighted_stroke(self):
        if self.sketch_canvas.edit_mode:
            self.exit_edit_mode()
            
        if self.sketch_canvas.highlighted_id is None:
            messagebox.showinfo("tip", "Please select the line to edit first")
            return
        
        highlighted_index = None
        for i, stroke in enumerate(self.strokes):
            if stroke.get('id', '') == self.sketch_canvas.highlighted_id:
                highlighted_index = i
                break
        
        if highlighted_index is not None:
            stroke = self.strokes[highlighted_index]
            points = stroke.get('points', [])
            
            pixel_points = []
            for point in points:
                try:
                    x, y = self.parse_point(point)
                    pixel_points.append((x, y))
                except Exception as e:
                    print(f"Error in parsing {point}: {e}")
            
            if pixel_points:
                self.sketch_canvas.enter_edit_mode(highlighted_index, pixel_points)
                self.status_var.set(f"Editing line {self.sketch_canvas.highlighted_id}")
            else:
                messagebox.showinfo("tip", "There is no valid point in this line")
        else:
            messagebox.showinfo("tip", "No line to be edited")  
    
    def edit_highlighted_skeleton(self):
        if self.sketch_canvas.edit_mode:
            self.exit_edit_mode()
        if self.sketch_canvas.highlighted_id is None:
            messagebox.showinfo("tip", "Please select the line to edit first")
            return
        if not self.skeleton_visible:
            self.skeleton_visible = True
            self.sketch_canvas.show_skeletons(self.skeleton_visible)

        highlighted_index = None
        for i, stroke in enumerate(self.strokes):
            if stroke.get('id', '') == self.sketch_canvas.highlighted_id:
                highlighted_index = i
                break
        if highlighted_index is not None:
            stroke = self.strokes[highlighted_index]
            points = stroke.get('skeleton', [])
            pixel_points = []
            for point in points:
                try:
                    x, y = self.parse_point(point)
                    pixel_points.append((x, y))
                except Exception as e:
                    print(f"Error in parsing {point}: {e}")
            
            if pixel_points:
                self.sketch_canvas.enter_edit_mode(highlighted_index, pixel_points,is_skeleton=True)
                self.status_var.set(f"Editing the skeleton {self.sketch_canvas.highlighted_id}")
            else:
                messagebox.showinfo("tip", "There is no valid point in this line")
        else:
            messagebox.showinfo("tip", "No line to be edited")
    def edit_highlighted_layout(self):
        if self.sketch_canvas.edit_mode:
            self.exit_edit_mode()
        if self.sketch_canvas.highlighted_layout_id is None:
            messagebox.showinfo("Prompt", "Please select the layout box to edit first")
            return

        
        if self.layouts_before == []:
            self.layouts_before = copy.deepcopy(self.layouts)

        self.sketch_canvas.edit_layout = True
        layout_id = self.sketch_canvas.highlighted_layout_id
        if self.sketch_canvas.layout_item_ids[layout_id] is None:
            messagebox.showinfo("tip", "The corresponding layout rectangle cannot be found")
            return
        
        rect_id = self.sketch_canvas.layout_item_ids[layout_id][0]
        coords = self.sketch_canvas.coords(rect_id)  
        x1, y1, x2, y2 = coords
        r = self.sketch_canvas.control_point_radius
        handles = [
            (x1, y1),       
            ((x1+x2)/2, y1),    
            (x2, y1),           
            (x2, (y1+y2)/2),   
            (x2, y2),          
            ((x1+x2)/2, y2),  
            (x1, y2),       
            (x1, (y1+y2)/2), 
        ]
        self.sketch_canvas.delete("layout_handle")
        self.sketch_canvas.layout_handle_ids = []
        for i, (cx, cy) in enumerate(handles):
            handle = self.sketch_canvas.create_oval(
                cx - r, cy - r, cx + r, cy + r,
                fill="blue", tags=("preview", "layout_handle", f"handle_{i}")
            )
            self.sketch_canvas.layout_handle_ids.append(handle)
        self.sketch_canvas.active_layout_index = layout_id
        self.sketch_canvas.layout_drag_handle = None 

    def exit_edit_mode(self):
        if self.sketch_canvas.active_skeleton_index != -1:
            stroke_id = self.sketch_canvas.active_skeleton_index
            
            response = messagebox.askyesno("tip", "Does the corresponding new strokes automatically generate based on the skeleton editing results?")
            if response:
                user_input = simpledialog.askstring("Input editing requirements", "Please enter the text description of this stroke:")
                self.edit_mode.set(value="edit_skeleton")
                
                stroke = self.strokes[stroke_id]
                stroke_name = stroke.get("id","")

                sk_data = []
                for sk in stroke.get('skeleton',[]):
                    try:
                        x, y = self.parse_point(sk)
                        sk_data.append({'x':x, 'y': y})
                    except Exception as e:
                        print(f"Error in parsing point {sk}: {e}")
                sim_data = []
                for pt in stroke.get('points', []):
                    try:
                        x, y = self.parse_point(pt)
                        sim_data.append({'x': x, 'y': y})
                    except Exception as e:
                        print(f"Error in parsing point {pt}: {e}")    
                editsk_msg = f"User edit the <skeleton> of stroke <s{stroke_id+1}>, you must generate a new corresponding <points> contour based on the updated skeleton. The original <s{stroke_id+1}>: "
                editsk_msg += f"\t<s{stroke_id+1}>\n" +self.parse_stroke_from_canvas(sk_data,sim_data,stroke_name) + f"</s{stroke_id+1}>\n"
                
                self.sketch_canvas.exit_edit_mode(register=True,skeleton_id=stroke_id)
                self.status_var.set("Exited skeleton editing mode")
                editsk_msg += f"User updates its skeleton to: "

                updated_stroke = self.strokes[stroke_id]
                sk_data = []
                for sk in updated_stroke.get('skeleton',[]):
                    x, y = self.parse_point(sk)
                    sk_data.append({'x':x, 'y': y})

                cur_user_input_stroke = f"<skeleton>"
                cur_skeleton = []
                for point_data in sk_data:
                    x, y = point_data['x'], point_data['y']
                    x = min(600, max(1, x))
                    y = min(600, max(1, y))
                    point_str = f'x{x}y{y}'
                    if (not cur_skeleton) or (cur_skeleton[-1] != point_str):
                        cur_skeleton.append(point_str)
                        cur_user_input_stroke += f"'{point_str}', "
                cur_user_input_stroke = cur_user_input_stroke[:-2]

                cur_user_input_stroke += f"</skeleton>\n"
                editsk_msg += cur_user_input_stroke + f"Please give a reasonable updated points for <s{stroke_id+1}> and the request is: {user_input}"

                self.send_message(message=editsk_msg)
            else:
                self.sketch_canvas.exit_edit_mode(register=True,skeleton_id=stroke_id)
                self.status_var.set("Exited skeleton editing mode and save manual editing results")
        elif self.sketch_canvas.active_stroke_index != -1:
            stroke_id = self.sketch_canvas.active_stroke_index
            self.sketch_canvas.exit_edit_mode(register=True,stroke_id=stroke_id)
            self.status_var.set("Exited Line Edit Mode")
        elif self.sketch_canvas.active_layout_index != -1:
            layout_id = self.sketch_canvas.active_layout_index
            self.editing_layout[layout_id] = self.layouts_before[layout_id]
            self.sketch_canvas.exit_edit_mode(register=True,layout_id=layout_id)
            response = messagebox.askyesnocancel("tip","yes: Automatically generate the corresponding new strokes based on the layout editing results; no: directly save the editing results; cancel: continue to edit other layouts")
            if response == True:
                user_input = simpledialog.askstring("Input editing requirements", "Please enter the text description requirements for layout editing:")
                self.edit_mode.set(value="edit_layout")
                
                msg_1 = ""
                msg_2 = ""
                for cur_layout_id,layout_ori_info in self.editing_layout.items():
                    new_rec = self.layouts[cur_layout_id].get("rec","")
                    ori_name = layout_ori_info.get("meaning","")
                    ori_sgroup = layout_ori_info.get("s_group","")
                    ori_rec = layout_ori_info.get("rec","")
                    sgroup_str = ""
                    for sid in ori_sgroup:
                        sgroup_str += f"s{sid}," 
                    ori_rec_str = f"x{ori_rec[0]}y{ori_rec[1]}, x{ori_rec[2]}y{ori_rec[3]}"
                    new_rec_str = f"x{new_rec[0]}y{new_rec[1]}, x{new_rec[2]}y{new_rec[3]}"
                    msg_1 += f"<l{cur_layout_id+1}>"
                    msg_2 += f"The original <l{cur_layout_id+1}>: <meaning>{ori_name}</meaning> <s_group>{sgroup_str[:-1]}</s_group> <rec>{ori_rec_str}</rec>. The user updates the <rec> of <l{cur_layout_id+1}> to: <rec>{new_rec_str}</rec>. "

                editlo_msg = f"User edit the {msg_1} in <layout>, you must edit the corresponding strokes in <s_group> to fit the change of <rec> for the mentioned layouts.\n{msg_2}\n"
                
                editlo_msg += f"Please give a reasonable updated sketch and the request is: {user_input}"

                self.send_message(message=editlo_msg)
                self.editing_layout = {}
                self.layouts_before = []
            elif response == False:
                self.status_var.set("Exited Layout Edit Mode")
                self.editing_layout = {}
                self.layouts_before = []
            elif response == None:
                self.show_layout_panel_popup()
                self.status_var.set("Continue to edit other layouts")

    def show_layout_panel_popup(self):
        popup = tk.Toplevel()
        popup.title("Select a Layout")
        popup.geometry("200x400") 
        popup.grab_set()

        def wrapped_callback(layout_id):
            self.select_layout_from_popup(layout_id, popup)

        new_layout_panel = LayoutPanel(popup, highlight_callback=wrapped_callback)
        new_layout_panel.pack(fill=tk.BOTH, expand=True)
        new_layout_panel.pack_propagate(False)

        layout_ids = list(range(len(self.layouts)))
        layout_names = []
        for layout in self.layouts:
            layout_name = layout.get('meaning', '')
            if not layout_name:
                layout_name = f"layout {len(layout_names) + 1}"
            layout_names.append(layout_name)
        
        new_layout_panel.update_layouts(layout_ids, layout_names)

        close_btn = tk.Button(popup, text="close", command=popup.destroy)
        close_btn.pack(pady=5)

    def select_layout_from_popup(self, layout_id,popup):
        self.exit_edit_mode()
        self.highlight_layout(layout_id)
        self.edit_highlighted_layout()
        try:
            popup.unbind_all("<MouseWheel>")
        except Exception:
            pass

        popup.destroy()

    
    def on_stroke_edited(self, stroke_index, points):
        if 0 <= stroke_index < len(self.strokes):
            stroke = self.strokes[stroke_index]

            was_in_edit_mode = self.sketch_canvas.edit_mode

            original_pixel_points = points.copy()

            grid_points = [f"x{x}y{y}" for x, y in points]

            if self.sketch_canvas.active_skeleton_index != -1:
                active_skeleton_index = self.sketch_canvas.active_skeleton_index
                stroke['skeleton'] = grid_points

                self.sketch_canvas.exit_edit_mode()

                if was_in_edit_mode and active_skeleton_index >= 0 and len(original_pixel_points) > 0:
                    self.sketch_canvas.enter_edit_mode(active_skeleton_index, original_pixel_points,is_skeleton=True)
                self.status_var.set(f"Updated skeleton {stroke.get('id', '')}")

            elif self.sketch_canvas.active_stroke_index != -1:
                active_stroke_index = self.sketch_canvas.active_stroke_index
                stroke['points'] = grid_points

                self.sketch_canvas.exit_edit_mode()

                if was_in_edit_mode and active_stroke_index >= 0 and len(original_pixel_points) > 0:
                    self.sketch_canvas.enter_edit_mode(active_stroke_index, original_pixel_points)

                self.status_var.set(f"Updated line {stroke.get('id', '')}")
            self.update_strokes_text()

    def on_layout_edited(self,rect_coords,layout_id):
        if 0 <= layout_id <=len(self.layouts):
            x1,y1,x2,y2 = rect_coords
            rec_xyxy = [
                int(x1), int(y1),
                int(x2), int(y2)
                ]
            self.layouts[layout_id]['rec'] = rec_xyxy
            self.status_var.set(f"Save layout {self.layouts[layout_id]['meaning']} Edited coordinates")
            self.update_strokes_text()


    def start_selection_mode(self):
        self.clear_highlight()
        self.sketch_canvas.enter_selection_mode()
        self.status_var.set("Box Select Mode: Please drag the mouse to select an area")
    
    def clear_selection(self):
        self.sketch_canvas.exit_selection_mode()
        self.current_selection = None
        self.current_grid_selection = None
        self.status_var.set("Cleared box selected area")
    
    def on_selection_complete(self, pixel_selection, grid_selection):
        self.current_selection = pixel_selection
        self.current_grid_selection = grid_selection
        self.status_var.set(f"Selected area: upper left ({grid_selection[0]},{grid_selection[1]}) Lower right ({grid_selection[2]},{grid_selection[3]})")
        self.chat_panel.add_system_message(f"Selected area x{grid_selection[0]}-x{grid_selection[2]}, y{grid_selection[1]}-y{grid_selection[3]}, in the message you can describe what you want to add in that area.")

    def on_status_show(self,info):
        self.status_var.set(info)
    
    
    def highlight_stroke_by_id(self, stroke_id):
        for i, stroke in enumerate(self.strokes):
            if str(stroke_id) == str(i+1):
                self.highlight_stroke(i, stroke.get('id', ''))
                return
            
    def highlight_layout_by_id(self,layout_id):
        for i, layout in enumerate(self.layouts):
            if str(layout_id) == str(i+1):
                self.highlight_layout(i)
                return
            
    def update_temp_label(self, *args):
        self.temp_value_label.config(text=f"{self.temperature.get():.2f}")

def main():
    root = tk.Tk()
    app = InteractiveSketchApp(root)
    if os.getenv("ANTHROPIC_API_KEY") == "" or os.getenv("ANTHROPIC_API_KEY")[:3] != "sk-":
        messagebox.WARNING("WARNING","Before running the CoSketcher, please create an Anthropic account and follow the instructions to obtain a key!")
        return
    root.mainloop()

if __name__ == "__main__":
    main() 