from dataclasses import dataclass

import esphome.codegen as cg
from esphome.components.const import CONF_BYTE_ORDER
from esphome.components.image import (
    IMAGE_TYPE,
    Image_,
    validate_settings,
    validate_transparency,
    validate_type,
)
import esphome.config_validation as cv
from esphome.const import CONF_FORMAT, CONF_ID, CONF_RESIZE, CONF_TYPE
from esphome.core import CORE

AUTO_LOAD = ["image"]
CODEOWNERS = ["@guillempages", "@clydebarrow", "@kahrendt"]

CONF_PLACEHOLDER = "placeholder"
CONF_TRANSPARENCY = "transparency"

runtime_image_ns = cg.esphome_ns.namespace("runtime_image")

# Base decoder classes
ImageDecoder = runtime_image_ns.class_("ImageDecoder")
BmpDecoder = runtime_image_ns.class_("BmpDecoder", ImageDecoder)
JpegDecoder = runtime_image_ns.class_("JpegDecoder", ImageDecoder)
PngDecoder = runtime_image_ns.class_("PngDecoder", ImageDecoder)

# Runtime image class
RuntimeImage = runtime_image_ns.class_(
    "RuntimeImage", cg.esphome_ns.namespace("image").class_("Image")
)

# Image format enum
ImageFormat = runtime_image_ns.enum("ImageFormat")
IMAGE_FORMAT_AUTO = ImageFormat.AUTO
IMAGE_FORMAT_JPEG = ImageFormat.JPEG
IMAGE_FORMAT_PNG = ImageFormat.PNG
IMAGE_FORMAT_BMP = ImageFormat.BMP

# Export enum for decode errors
DecodeError = runtime_image_ns.enum("DecodeError")
DECODE_ERROR_INVALID_TYPE = DecodeError.DECODE_ERROR_INVALID_TYPE
DECODE_ERROR_UNSUPPORTED_FORMAT = DecodeError.DECODE_ERROR_UNSUPPORTED_FORMAT
DECODE_ERROR_OUT_OF_MEMORY = DecodeError.DECODE_ERROR_OUT_OF_MEMORY


class Format:
    """Base class for image format definitions."""

    def __init__(self, name: str, decoder_class: cg.MockObjClass) -> None:
        self.name = name
        self.decoder_class = decoder_class

    def actions(self) -> None:
        """Add defines and libraries needed for this format."""


class BMPFormat(Format):
    """BMP format decoder configuration."""

    def __init__(self):
        super().__init__("BMP", BmpDecoder)

    def actions(self) -> None:
        cg.add_define("USE_RUNTIME_IMAGE_BMP")


class JPEGFormat(Format):
    """JPEG format decoder configuration."""

    def __init__(self):
        super().__init__("JPEG", JpegDecoder)

    def actions(self) -> None:
        cg.add_define("USE_RUNTIME_IMAGE_JPEG")
        cg.add_library("JPEGDEC", "1.8.4", "https://github.com/bitbank2/JPEGDEC#1.8.4")
        if CORE.is_esp32:
            from esphome.components.esp32 import add_idf_component

            # JPEGDEC uses ESP32-S3 SIMD optimizations (guarded by board-level
            # ARDUINO_ESP32S3_DEV define) that require esp-dsp headers.
            # On Arduino this overwrites the stub; on IDF it adds the component.
            add_idf_component(name="espressif/esp-dsp", ref="1.7.1")


class PNGFormat(Format):
    """PNG format decoder configuration."""

    def __init__(self):
        super().__init__("PNG", PngDecoder)

    def actions(self) -> None:
        cg.add_define("USE_RUNTIME_IMAGE_PNG")
        cg.add_library("pngle", "1.1.0")


# Registry of available formats
IMAGE_FORMATS = {
    "BMP": BMPFormat(),
    "JPEG": JPEGFormat(),
    "PNG": PNGFormat(),
    "JPG": JPEGFormat(),  # Alias for JPEG
}


def get_format(format_name: str) -> Format | None:
    """Get a format instance by name."""
    return IMAGE_FORMATS.get(format_name.upper())


def enable_format(format_name: str) -> Format | None:
    """Enable a specific image format by adding its defines and libraries."""
    format_obj = get_format(format_name)
    if format_obj:
        format_obj.actions()
        return format_obj
    return None


# Runtime image configuration schema base - to be extended by components
def runtime_image_schema(image_class: cg.MockObjClass = RuntimeImage) -> cv.Schema:
    """Create a runtime image schema with the specified image class."""
    return cv.Schema(
        {
            cv.Required(CONF_ID): cv.declare_id(image_class),
            cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, upper=True),
            cv.Optional(CONF_RESIZE): cv.dimensions,
            cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE),
            cv.Optional(CONF_BYTE_ORDER): cv.one_of(
                "BIG_ENDIAN", "LITTLE_ENDIAN", upper=True
            ),
            cv.Optional(CONF_TRANSPARENCY, default="OPAQUE"): validate_transparency(),
            cv.Optional(CONF_PLACEHOLDER): cv.use_id(Image_),
        }
    )


def validate_runtime_image_settings(config: dict) -> dict:
    """Apply validate_settings from image component to runtime image config."""
    return validate_settings(config)


@dataclass
class RuntimeImageSettings:
    """Processed runtime image configuration parameters."""

    width: int
    height: int
    format_enum: cg.MockObj
    image_type_enum: cg.MockObj
    transparent: cg.MockObj
    byte_order_big_endian: bool
    placeholder: cg.MockObj | None


async def process_runtime_image_config(config: dict) -> RuntimeImageSettings:
    """
    Helper function to process common runtime image configuration parameters.
    Handles format enabling and returns all necessary enums and parameters.
    """
    from esphome.components.image import get_image_type_enum, get_transparency_enum

    # Get resize dimensions with default (0, 0)
    width, height = config.get(CONF_RESIZE, (0, 0))

    # Handle format (required for runtime images)
    format_name = config[CONF_FORMAT]
    # Enable the format in the runtime_image component
    enable_format(format_name)
    # Map format names to enum values (handle JPG as alias for JPEG)
    if format_name.upper() == "JPG":
        format_name = "JPEG"
    format_enum = getattr(ImageFormat, format_name.upper())

    # Get image type enum
    image_type_enum = get_image_type_enum(config[CONF_TYPE])

    # Get transparency enum
    transparent = get_transparency_enum(config.get(CONF_TRANSPARENCY, "OPAQUE"))

    # Get byte order (True for big endian, False for little endian)
    byte_order_big_endian = config.get(CONF_BYTE_ORDER) != "LITTLE_ENDIAN"

    # Get placeholder if specified
    placeholder = None
    if placeholder_id := config.get(CONF_PLACEHOLDER):
        placeholder = await cg.get_variable(placeholder_id)

    return RuntimeImageSettings(
        width=width,
        height=height,
        format_enum=format_enum,
        image_type_enum=image_type_enum,
        transparent=transparent,
        byte_order_big_endian=byte_order_big_endian,
        placeholder=placeholder,
    )
