from esphome import config_validation as cv
from esphome.const import CONF_BUTTON, CONF_ID, CONF_ITEMS, CONF_TEXT
from esphome.core import ID
from esphome.cpp_generator import new_Pvariable, static_const_array
from esphome.cpp_types import nullptr

from ..defines import (
    CONF_BODY,
    CONF_BUTTON_STYLE,
    CONF_BUTTONS,
    CONF_CLOSE_BUTTON,
    CONF_MSGBOXES,
    CONF_TITLE,
    TYPE_FLEX,
    literal,
)
from ..helpers import add_lv_use, lvgl_components_required
from ..lv_validation import lv_bool, lv_pct, lv_text
from ..lvcode import (
    EVENT_ARG,
    LambdaContext,
    LocalVariable,
    lv,
    lv_add,
    lv_assign,
    lv_expr,
    lv_obj,
    lv_Pvariable,
)
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, add_widgets, set_obj_properties
from .button import button_spec
from .buttonmatrix import (
    BUTTONMATRIX_BUTTON_SCHEMA,
    CONF_BUTTON_TEXT_LIST_ID,
    buttonmatrix_spec,
    get_button_data,
    lv_buttonmatrix_t,
    set_btn_data,
)
from .label import CONF_LABEL
from .obj import obj_spec

CONF_MSGBOX = "msgbox"
MSGBOX_SCHEMA = container_schema(
    obj_spec,
    STYLE_SCHEMA.extend(
        {
            cv.GenerateID(CONF_ID): cv.declare_id(lv_obj_t),
            cv.Required(CONF_TITLE): STYLED_TEXT_SCHEMA,
            cv.Optional(CONF_BODY, default=""): STYLED_TEXT_SCHEMA,
            cv.Optional(CONF_BUTTONS): cv.ensure_list(BUTTONMATRIX_BUTTON_SCHEMA),
            cv.Optional(CONF_BUTTON_STYLE): part_schema(buttonmatrix_spec.parts),
            cv.Optional(CONF_CLOSE_BUTTON, default=True): lv_bool,
            cv.GenerateID(CONF_BUTTON_TEXT_LIST_ID): cv.declare_id(char_ptr),
        }
    ),
)


async def msgbox_to_code(top_layer, conf):
    """
    Construct a message box. This consists of a full-screen translucent background enclosing a centered container
    with an optional title, body, close button and a button matrix. And any other widgets the user cares to add
    :param conf: The config data
    :return: code to add to the init lambda
    """
    add_lv_use(
        TYPE_FLEX,
        CONF_BUTTON,
        CONF_LABEL,
        CONF_MSGBOX,
        *buttonmatrix_spec.get_uses(),
        *button_spec.get_uses(),
    )
    lvgl_components_required.add("BUTTONMATRIX")
    messagebox_id = conf[CONF_ID]
    outer_id = f"{messagebox_id.id}_outer"
    outer = lv_Pvariable(lv_obj_t, messagebox_id.id + "_outer")
    buttonmatrix = new_Pvariable(
        ID(
            f"{messagebox_id.id}_buttonmatrix_",
            is_declaration=True,
            type=lv_buttonmatrix_t,
        )
    )
    msgbox = lv_Pvariable(lv_obj_t, messagebox_id.id)
    outer_widget = Widget.create(outer_id, outer, obj_spec, conf)
    outer_widget.move_to_foreground = True
    msgbox_widget = Widget.create(messagebox_id, msgbox, obj_spec, conf)
    msgbox_widget.outer = outer_widget
    buttonmatrix_widget = Widget.create(
        str(buttonmatrix), buttonmatrix, buttonmatrix_spec, conf
    )
    text_list, ctrl_list, width_list, _ = await get_button_data(
        (conf,), buttonmatrix_widget
    )
    text_id = conf[CONF_BUTTON_TEXT_LIST_ID]
    text_list = static_const_array(text_id, text_list)
    text = await lv_text.process(conf[CONF_BODY].get(CONF_TEXT, ""))
    title = await lv_text.process(conf[CONF_TITLE].get(CONF_TEXT, ""))
    close_button = conf[CONF_CLOSE_BUTTON]
    lv_assign(outer, lv_expr.obj_create(top_layer))
    lv_obj.set_width(outer, lv_pct(100))
    lv_obj.set_height(outer, lv_pct(100))
    lv_obj.set_style_bg_opa(outer, 128, 0)
    lv_obj.set_style_bg_color(outer, literal("lv_color_black()"), 0)
    lv_obj.set_style_border_width(outer, 0, 0)
    lv_obj.set_style_pad_all(outer, 0, 0)
    lv_obj.set_style_radius(outer, 0, 0)
    outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
    lv_assign(
        msgbox, lv_expr.msgbox_create(outer, title, text, text_list, close_button)
    )
    lv_obj.set_style_align(msgbox, literal("LV_ALIGN_CENTER"), 0)
    lv_add(buttonmatrix.set_obj(lv_expr.msgbox_get_btns(msgbox)))
    if button_style := conf.get(CONF_BUTTON_STYLE):
        button_style = {CONF_ITEMS: button_style}
        await set_obj_properties(buttonmatrix_widget, button_style)
    await set_obj_properties(msgbox_widget, conf)
    await add_widgets(msgbox_widget, conf)
    async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
        outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
    if close_button:
        with LocalVariable(
            "close_btn_", lv_obj_t, lv_expr.msgbox_get_close_btn(msgbox)
        ) as close_btn:
            lv_obj.remove_event_cb(close_btn, nullptr)
            lv_obj.add_event_cb(
                close_btn,
                await close_action.get_lambda(),
                LV_EVENT.CLICKED,
                nullptr,
            )
    else:
        lv_obj.add_event_cb(
            outer, await close_action.get_lambda(), LV_EVENT.CLICKED, nullptr
        )

    if len(ctrl_list) != 0 or len(width_list) != 0:
        set_btn_data(buttonmatrix.obj, ctrl_list, width_list)


async def msgboxes_to_code(lv_component, config):
    top_layer = lv.disp_get_layer_top(lv_component.get_disp())
    for conf in config.get(CONF_MSGBOXES, ()):
        await msgbox_to_code(top_layer, conf)
