"""Tk thread management, color palettes, and drawing helpers."""

import asyncio
import math
import threading
import turtle
import tkinter as tk
from typing import Callable

# ---------------------------------------------------------------------------
# Color palettes
# ---------------------------------------------------------------------------
PALETTES: dict[str, list[str]] = {
    "rainbow": ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3"],
    "sunset": ["#FF6B35", "#F7C59F", "#EFEFD0", "#004E89", "#1A659E"],
    "ocean": ["#003049", "#006D77", "#83C5BE", "#EDF6F9", "#FFDDD2"],
    "forest": ["#1B4332", "#2D6A4F", "#40916C", "#52B788", "#95D5B2"],
    "pastel": ["#FFB3BA", "#FFDFBA", "#FFFFBA", "#BAFFC9", "#BAE1FF"],
    "jewel": ["#6A0572", "#AB83A1", "#E6C79C", "#F7DB4F", "#2F9599"],
    "autumn": ["#A63D40", "#E9B44C", "#9B2335", "#50723C", "#E8871E"],
    "cherry": ["#FFB7C5", "#FF69B4", "#FF1493", "#C71585", "#8B0045"],
    "earth": ["#8B4513", "#A0522D", "#CD853F", "#DEB887", "#D2B48C"],
    "neon": ["#FF00FF", "#00FFFF", "#39FF14", "#FF073A", "#DFFF00"],
}

DEFAULT_PALETTE = "rainbow"


def get_palette(name: str | None) -> list[str]:
    """Return a palette by name, falling back to rainbow."""
    if name is None:
        return PALETTES[DEFAULT_PALETTE]
    return PALETTES.get(name.lower(), PALETTES[DEFAULT_PALETTE])


def palette_color(palette: list[str], index: int) -> str:
    """Cycle through palette colors."""
    return palette[index % len(palette)]


# ---------------------------------------------------------------------------
# Tk thread singleton
# ---------------------------------------------------------------------------
_tk_lock = threading.Lock()
_root: tk.Tk | None = None
_screen: turtle.TurtleScreen | None = None
_canvas: tk.Canvas | None = None
_tk_thread: threading.Thread | None = None
_tk_ready = threading.Event()


def _run_tk() -> None:
    """Entry point for the daemon thread that hosts tkinter."""
    global _root, _screen, _canvas

    _root = tk.Tk()
    _root.title("Turtle MCP")
    _root.geometry("800x800")
    _root.protocol("WM_DELETE_WINDOW", lambda: None)  # ignore close

    _canvas = tk.Canvas(_root, width=800, height=800)
    _canvas.pack(fill=tk.BOTH, expand=True)

    _screen = turtle.TurtleScreen(_canvas)
    _screen.bgcolor("white")
    _screen.tracer(0)

    _tk_ready.set()
    _root.mainloop()


def _ensure_tk() -> None:
    """Start the Tk daemon thread if it isn't running yet."""
    global _tk_thread
    with _tk_lock:
        if _tk_thread is None or not _tk_thread.is_alive():
            _tk_ready.clear()
            _tk_thread = threading.Thread(target=_run_tk, daemon=True)
            _tk_thread.start()
            _tk_ready.wait(timeout=10)


# ---------------------------------------------------------------------------
# execute_drawing  –  the bridge between async MCP and the Tk thread
# ---------------------------------------------------------------------------

async def execute_drawing(draw_fn: Callable[[turtle.RawTurtle, turtle.TurtleScreen], None]) -> str:
    """Schedule *draw_fn* on the Tk thread and wait for it to finish.

    Parameters
    ----------
    draw_fn : callable(t, screen)
        A function that receives a RawTurtle and the TurtleScreen
        and performs all drawing operations.

    Returns
    -------
    str   Success / error message returned to the LLM.
    """
    _ensure_tk()
    assert _root is not None and _screen is not None and _canvas is not None

    done = threading.Event()
    error: Exception | None = None

    def _on_tk_thread() -> None:
        nonlocal error
        try:
            _screen.clear()
            _screen.bgcolor("white")
            _screen.tracer(0)
            t = turtle.RawTurtle(_screen)
            t.speed(0)
            t.hideturtle()
            draw_fn(t, _screen)
            _screen.update()
        except Exception as exc:
            error = exc
        finally:
            done.set()

    _root.after(0, _on_tk_thread)

    # Wait in a non-blocking way so we don't stall the asyncio loop
    loop = asyncio.get_running_loop()
    await loop.run_in_executor(None, done.wait)

    if error is not None:
        return f"Drawing error: {error}"
    return "Drawing completed successfully."


# ---------------------------------------------------------------------------
# Drawing helpers
# ---------------------------------------------------------------------------

def make_turtle(screen: turtle.TurtleScreen) -> turtle.RawTurtle:
    """Create a fresh hidden, fastest-speed RawTurtle."""
    t = turtle.RawTurtle(screen)
    t.speed(0)
    t.hideturtle()
    return t


def draw_filled_circle(t: turtle.RawTurtle, x: float, y: float,
                        radius: float, color: str) -> None:
    """Draw a filled circle centred at (x, y)."""
    t.penup()
    t.goto(x, y - radius)
    t.pendown()
    t.fillcolor(color)
    t.begin_fill()
    t.circle(radius)
    t.end_fill()
    t.penup()


def draw_filled_rect(t: turtle.RawTurtle, x: float, y: float,
                      w: float, h: float, color: str) -> None:
    """Draw a filled rectangle with bottom-left at (x, y)."""
    t.penup()
    t.goto(x, y)
    t.pendown()
    t.fillcolor(color)
    t.pencolor(color)
    t.begin_fill()
    for side in (w, h, w, h):
        t.forward(side)
        t.left(90)
    t.end_fill()
    t.penup()


def lerp_color(c1: str, c2: str, t_val: float) -> str:
    """Linearly interpolate between two hex colors."""
    r1, g1, b1 = int(c1[1:3], 16), int(c1[3:5], 16), int(c1[5:7], 16)
    r2, g2, b2 = int(c2[1:3], 16), int(c2[3:5], 16), int(c2[5:7], 16)
    r = int(r1 + (r2 - r1) * t_val)
    g = int(g1 + (g2 - g1) * t_val)
    b = int(b1 + (b2 - b1) * t_val)
    return f"#{r:02x}{g:02x}{b:02x}"
