from functools import lru_cache

from PIL import Image, ImageColor, ImageDraw


FONT_FAMILY = "Georgia"

COLORS = {
    "bg_primary": "#151515",
    "bg_secondary": "#1d1d1d",
    "bg_tertiary": "#212121",
    "bg_card": "#2b2b2b",
    "bg_hover": "#36312c",
    "bg_error": "#2f2926",
    "text_primary": "#f5f2ec",
    "text_highlight": "#f0a621",
    "text_body": "#c8c8c8",
    "text_secondary": "#ffffff",
    "text_muted": "#979797",
    "accent_green": "#3a8e35",
    "accent_red": "#ff4a46",
    "accent_yellow": "#f0a621",
    "accent_blue": "#d7b56a",
    "border_light": "#4d453b",
    "border_dark": "#0f0f0f",
    "stripe": "#1d1d1d",
    "stripe_soft": "#232323",
}


@lru_cache(maxsize=4)
def _load_rgba_source(image_path):
    """Load and cache a source image in RGBA form for repeated resizes."""
    return Image.open(image_path).convert("RGBA")


def _rgba(color, alpha=255):
    red, green, blue = ImageColor.getrgb(color)
    return red, green, blue, alpha


def create_striped_texture(
    width,
    height,
    *,
    base_color=None,
    stripe_color=None,
    stripe_width=16,
    stripe_gap=18,
    stripe_alpha=110,
    corner_radius=0,
    border_color=None,
    border_width=0,
    border_alpha=255,
):
    """Create a dark diagonal striped texture matching the pack UI style."""
    width = max(1, int(width))
    height = max(1, int(height))

    base = Image.new(
        "RGBA",
        (width, height),
        _rgba(base_color or COLORS["bg_card"]),
    )

    stripes = Image.new("RGBA", (width, height), (0, 0, 0, 0))
    stripe_draw = ImageDraw.Draw(stripes)
    span = width + height
    step = max(1, stripe_width + stripe_gap)

    for offset in range(-height, span + height, step):
        stripe_draw.line(
            (offset, 0, offset + height, height),
            fill=_rgba(stripe_color or COLORS["stripe"], stripe_alpha),
            width=max(1, stripe_width),
        )

    image = Image.alpha_composite(base, stripes)

    if border_color and border_width > 0:
        border_draw = ImageDraw.Draw(image)
        inset = max(1, border_width // 2)
        bounds = (inset, inset, width - inset - 1, height - inset - 1)
        if corner_radius > 0:
            border_draw.rounded_rectangle(
                bounds,
                radius=max(1, corner_radius - inset),
                outline=_rgba(border_color, border_alpha),
                width=border_width,
            )
        else:
            border_draw.rectangle(
                bounds,
                outline=_rgba(border_color, border_alpha),
                width=border_width,
            )

    if corner_radius > 0:
        mask = Image.new("L", (width, height), 0)
        mask_draw = ImageDraw.Draw(mask)
        mask_draw.rounded_rectangle(
            (0, 0, width - 1, height - 1),
            radius=corner_radius,
            fill=255,
        )
        clipped = Image.new("RGBA", (width, height), (0, 0, 0, 0))
        clipped.paste(image, (0, 0), mask)
        image = clipped

    return image


def create_striped_panel(
    width,
    height,
    *,
    panel_color=None,
    stripe_color=None,
    stripe_width=18,
    stripe_gap=18,
    stripe_alpha=110,
    halo_padding=22,
    halo_alpha=135,
    panel_alpha=240,
    corner_radius=18,
    border_color=None,
    border_width=1,
    border_alpha=255,
):
    """Create a mostly solid panel with stripes that extend just beyond its edges."""
    width = max(1, int(width))
    height = max(1, int(height))
    halo_padding = max(0, int(halo_padding))
    total_width = width + (halo_padding * 2)
    total_height = height + (halo_padding * 2)

    image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))

    stripes = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
    stripe_draw = ImageDraw.Draw(stripes)
    span = total_width + total_height
    step = max(1, stripe_width + stripe_gap)

    for offset in range(-total_height, span + total_height, step):
        stripe_draw.line(
            (offset, 0, offset + total_height, total_height),
            fill=_rgba(stripe_color or COLORS["stripe"], halo_alpha),
            width=max(1, stripe_width),
        )

    halo_mask = Image.new("L", (total_width, total_height), 0)
    halo_draw = ImageDraw.Draw(halo_mask)
    halo_draw.rounded_rectangle(
        (0, 0, total_width - 1, total_height - 1),
        radius=max(1, corner_radius + halo_padding),
        fill=255,
    )
    halo_image = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
    halo_image.paste(stripes, (0, 0), halo_mask)
    image = Image.alpha_composite(image, halo_image)

    panel_left = halo_padding
    panel_top = halo_padding
    panel_right = panel_left + width - 1
    panel_bottom = panel_top + height - 1

    panel = Image.new("RGBA", (total_width, total_height), (0, 0, 0, 0))
    panel_draw = ImageDraw.Draw(panel)
    panel_draw.rounded_rectangle(
        (panel_left, panel_top, panel_right, panel_bottom),
        radius=corner_radius,
        fill=_rgba(panel_color or COLORS["bg_card"], panel_alpha),
    )

    if border_color and border_width > 0:
        inset = max(1, border_width // 2)
        panel_draw.rounded_rectangle(
            (
                panel_left + inset,
                panel_top + inset,
                panel_right - inset,
                panel_bottom - inset,
            ),
            radius=max(1, corner_radius - inset),
            outline=_rgba(border_color, border_alpha),
            width=border_width,
        )

    image = Image.alpha_composite(image, panel)
    return image


def load_cover_background(image_path, width, height, *, overlay_color=None, overlay_alpha=125):
    """Load an image, scale it to cover, and apply a dark overlay for readability."""
    width = max(1, int(width))
    height = max(1, int(height))

    image = _load_rgba_source(image_path)
    source_ratio = image.width / image.height
    target_ratio = width / height

    if source_ratio > target_ratio:
        new_height = height
        new_width = int(height * source_ratio)
    else:
        new_width = width
        new_height = int(width / source_ratio)

    image = image.resize((new_width, new_height), Image.Resampling.BICUBIC)
    left = (new_width - width) // 2
    top = (new_height - height) // 2
    image = image.crop((left, top, left + width, top + height))

    overlay = Image.new(
        "RGBA",
        (width, height),
        _rgba(overlay_color or COLORS["bg_primary"], overlay_alpha),
    )
    return Image.alpha_composite(image, overlay)


def style_text_button(
    button,
    foreground,
    background,
    *,
    hover_foreground=None,
    disabled_foreground=None,
):
    """Style a tkinter Button to look like a color-coded text action."""
    button.configure(
        bg=background,
        fg=foreground,
        activebackground=background,
        activeforeground=hover_foreground or COLORS["text_primary"],
        relief="flat",
        bd=0,
        borderwidth=0,
        highlightthickness=0,
        disabledforeground=disabled_foreground or COLORS["text_muted"],
        cursor="hand2",
    )


def center_window(window, width, height, parent=None):
    """Center a window either on its parent or the current screen."""
    width = int(width)
    height = int(height)
    window.update_idletasks()

    if parent is not None and parent.winfo_exists():
        x = parent.winfo_rootx() + (parent.winfo_width() // 2) - (width // 2)
        y = parent.winfo_rooty() + (parent.winfo_height() // 2) - (height // 2)
    else:
        x = (window.winfo_screenwidth() // 2) - (width // 2)
        y = (window.winfo_screenheight() // 2) - (height // 2)

    window.geometry(f"{width}x{height}+{max(0, x)}+{max(0, y)}")
