import math
import os
from io import BytesIO

from PIL import Image


def _to_grayscale_with_alpha(img: Image.Image) -> Image.Image:
    if img.mode in ("RGBA", "LA") or ("transparency" in img.info):
        img_rgba = img.convert("RGBA")
        white_bg = Image.new("RGBA", img_rgba.size, (255, 255, 255, 255))
        white_bg.alpha_composite(img_rgba)
        return white_bg.convert("L")
    return img.convert("L")


def load_image(path: str, width: int, height: int) -> Image.Image:
    ext = os.path.splitext(path)[1].lower()

    if ext in {".svg", ".svgz"}:
        try:
            import cairosvg  # type: ignore
        except ImportError as exc:  # pragma: no cover - import-time
            raise RuntimeError(
                "SVG input requires cairosvg. Install it via 'pip install cairosvg'."
            ) from exc

        png_bytes = cairosvg.svg2png(url=path, output_width=width, output_height=height)
        img = Image.open(BytesIO(png_bytes))
        img = _to_grayscale_with_alpha(img)
        return img

    img = Image.open(path)
    img = _to_grayscale_with_alpha(img)
    src_w, src_h = img.size

    scale = min(width / src_w, height / src_h)
    if scale <= 0:
        raise ValueError("Invalid target size for image")

    new_w = max(1, int(round(src_w * scale)))
    new_h = max(1, int(round(src_h * scale)))
    img_resized = img.resize((new_w, new_h), Image.BILINEAR)

    canvas = Image.new("L", (width, height), 255)
    offset_x = (width - new_w) // 2
    offset_y = (height - new_h) // 2
    canvas.paste(img_resized, (offset_x, offset_y))
    return canvas


def to_monochrome_bytes(img: Image.Image, threshold_level: int) -> bytes:
    width, height = img.size
    threshold = int(round((threshold_level / 10.0) * 255))

    pixels = img.load()
    pages = math.ceil(height / 8)
    result = bytearray()

    for page in range(pages):
        for x in range(width):
            byte = 0
            for bit in range(8):
                y = page * 8 + bit
                if y >= height:
                    continue
                value = pixels[x, y]
                if value < threshold:
                    byte |= 1 << bit
            result.append(byte)

    return bytes(result)


def bytes_to_bool_matrix(width: int, height: int, data: bytes) -> list[list[int]]:
    pages = math.ceil(height / 8)
    expected_len = width * pages
    if len(data) < expected_len:
        raise ValueError("Data length is too short for given width/height")

    bitmap: list[list[int]] = [[0 for _ in range(width)] for _ in range(height)]
    idx = 0
    for page in range(pages):
        for x in range(width):
            byte = data[idx]
            idx += 1
            for bit in range(8):
                y = page * 8 + bit
                if y >= height:
                    continue
                bitmap[y][x] = (byte >> bit) & 0x01
    return bitmap


def bool_matrix_to_bytes(width: int, height: int, bitmap: list[list[int]]) -> bytes:
    pages = math.ceil(height / 8)
    result = bytearray()
    for page in range(pages):
        for x in range(width):
            byte = 0
            for bit in range(8):
                y = page * 8 + bit
                if y >= height:
                    continue
                if bitmap[y][x]:
                    byte |= 1 << bit
            result.append(byte)
    return bytes(result)


def interactive_edit_bitmap(width: int, height: int, bitmap: list[list[int]], scale: int) -> list[list[int]]:
    if scale <= 0:
        return bitmap

    try:
        import tkinter as tk
    except Exception:  # pragma: no cover - GUI not always available
        return bitmap

    edited = [row[:] for row in bitmap]

    root = tk.Tk()
    root.title("aDot bitmap editor")

    canvas_w = width * scale
    canvas_h = height * scale
    canvas = tk.Canvas(root, width=canvas_w, height=canvas_h)
    canvas.pack(side=tk.TOP)

    rect_ids: list[list[int | None]] = [[None for _ in range(width)] for _ in range(height)]

    def draw_pixel(x: int, y: int) -> None:
        color = "black" if edited[y][x] else "white"
        x0 = x * scale
        y0 = y * scale
        x1 = x0 + scale
        y1 = y0 + scale
        if rect_ids[y][x] is None:
            rect_ids[y][x] = canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="gray")
        else:
            canvas.itemconfig(rect_ids[y][x], fill=color)

    for y in range(height):
        for x in range(width):
            draw_pixel(x, y)

    def on_click(event: "tk.Event") -> None:  # type: ignore[name-defined]
        x = event.x // scale
        y = event.y // scale
        if 0 <= x < width and 0 <= y < height:
            edited[y][x] = 0 if edited[y][x] else 1
            draw_pixel(x, y)

    canvas.bind("<Button-1>", on_click)

    result = {"ok": False}

    def on_ok() -> None:
        result["ok"] = True
        root.destroy()

    def on_cancel() -> None:
        root.destroy()

    button_frame = tk.Frame(root)
    button_frame.pack(side=tk.BOTTOM, pady=8)
    tk.Button(button_frame, text="OK", command=on_ok).pack(side=tk.LEFT, padx=4)
    tk.Button(button_frame, text="Cancel", command=on_cancel).pack(side=tk.LEFT, padx=4)

    root.mainloop()

    return edited if result["ok"] else bitmap


def preview_from_bytes(width: int, height: int, data: bytes) -> None:
    img = Image.new("L", (width, height), 0)
    pixels = img.load()

    pages = math.ceil(height / 8)
    expected_len = width * pages
    if len(data) < expected_len:
        return

    idx = 0
    for page in range(pages):
        for x in range(width):
            byte = data[idx]
            idx += 1
            for bit in range(8):
                y = page * 8 + bit
                if y >= height:
                    continue
                bit_val = (byte >> bit) & 0x01
                pixels[x, y] = 255 if bit_val == 1 else 0

    img.show("preview")


def format_hexdump(height: int, width: int, data: bytes) -> str:
    all_bytes = bytearray()
    all_bytes.append(height & 0xFF)
    all_bytes.append(width & 0xFF)
    all_bytes.extend(data)

    lines = ["const uint8_t data[] =", "    {"]
    for i in range(0, len(all_bytes), 16):
        chunk = all_bytes[i : i + 16]
        hex_bytes = ",".join(f"0x{b:02X}" for b in chunk)
        if i + 16 < len(all_bytes):
            lines.append(f"        {hex_bytes},")
        else:
            lines.append(f"        {hex_bytes}")
    lines.append("    };")
    return "\n".join(lines)
