from operator import itemgetter

from esphome import config_validation as cv
import esphome.codegen as cg
from esphome.const import (
    CONF_COLOR,
    CONF_DIRECTION,
    CONF_DITHER,
    CONF_ID,
    CONF_POSITION,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj

from .defines import (
    CONF_GRADIENTS,
    CONF_OPA,
    LV_DITHER,
    add_define,
    add_lv_use,
    add_warning,
)
from .lv_validation import lv_color, lv_percentage, opacity
from .lvcode import lv
from .types import lv_color_t, lv_gradient_t, lv_opa_t

CONF_STOPS = "stops"


def min_stops(value):
    if len(value) < 2:
        raise cv.Invalid("Must have at least 2 stops")
    return value


GRADIENT_SCHEMA = cv.ensure_list(
    cv.Schema(
        {
            cv.GenerateID(CONF_ID): cv.declare_id(lv_gradient_t),
            cv.Required(CONF_DIRECTION): cv.one_of(
                "HOR", "HORIZONTAL", "VER", "VERTICAL", upper=True
            ),
            cv.Optional(CONF_DITHER): LV_DITHER.one_of,
            cv.Required(CONF_STOPS): cv.All(
                [
                    cv.Schema(
                        {
                            cv.Required(CONF_COLOR): lv_color,
                            cv.Optional(CONF_OPA, default=1.0): opacity,
                            cv.Required(CONF_POSITION): lv_percentage,
                        }
                    )
                ],
                min_stops,
            ),
        }
    )
)


async def gradients_to_code(config):
    add_lv_use("gradient")
    max_stops = 2
    if any(CONF_DITHER in x for x in config.get(CONF_GRADIENTS, ())):
        add_warning(
            "The 'dither' option for gradients is not supported by LVGL 9.x and will be ignored"
        )
    for gradient in config.get(CONF_GRADIENTS, ()):
        var = MockObj(cg.new_Pvariable(gradient[CONF_ID]), "->")
        idbase = gradient[CONF_ID].id
        stops = sorted(gradient[CONF_STOPS], key=itemgetter(CONF_POSITION))
        max_stops = max(max_stops, len(stops))
        if gradient[CONF_DIRECTION].startswith("VER"):
            lv.grad_vertical_init(var)
        else:
            lv.grad_horizontal_init(var)
        stop_colors = cg.static_const_array(
            ID(idbase + "_colors_", type=lv_color_t),
            [await lv_color.process(x[CONF_COLOR]) for x in stops],
        )
        stop_opacities = cg.static_const_array(
            ID(idbase + "_opacities_", type=lv_opa_t),
            [await opacity.process(x[CONF_OPA]) for x in stops],
        )
        stop_positions = cg.static_const_array(
            ID(idbase + "_positions_", type=cg.uint8),
            [await lv_percentage.process(x[CONF_POSITION]) for x in stops],
        )
        lv.grad_init_stops(var, stop_colors, stop_opacities, stop_positions, len(stops))

    add_define("LV_GRADIENT_MAX_STOPS", max_stops)
