"""Overcooked grid object types (food, pots, plates, delivery zones)."""

from cogrid.core import constants, grid_object
from cogrid.core.containers import Container, Recipe
from cogrid.core.grid_object import register_object_type
from cogrid.core.when import when
from cogrid.visualization.rendering import (
    add_text_to_image,
    fill_coords,
    point_in_circle,
)


@register_object_type("onion", scope="overcooked")
class Onion(grid_object.GridObj):
    """A single onion ingredient."""

    color = constants.Colors.Yellow
    char = "o"
    can_pickup = when()

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)

    def render(self, tile_img):
        """Draw a yellow circle."""
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.3), self.color)


@register_object_type("tomato", scope="overcooked")
class Tomato(grid_object.GridObj):
    """A single tomato ingredient."""

    color = constants.Colors.Red
    char = "t"
    can_pickup = when()

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)

    def render(self, tile_img):
        """Draw a red circle."""
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.3), self.color)


class _BaseStack(grid_object.GridObj):
    """Base class for infinite dispenser stacks.

    Subclasses set class attributes only: object_id, color, char, produces.
    All behavior (pick_up_from, render) is inherited.
    """

    produces: str = None
    scope: str = "overcooked"
    can_pickup_from = when()

    def render(self, tile_img):
        """Draw three stacked circles using self.color."""
        fill_coords(tile_img, point_in_circle(cx=0.25, cy=0.3, r=0.2), self.color)
        fill_coords(tile_img, point_in_circle(cx=0.75, cy=0.3, r=0.2), self.color)
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.7, r=0.2), self.color)


@register_object_type("onion_stack", scope="overcooked")
class OnionStack(_BaseStack):
    """An infinite pile of onions."""

    color = constants.Colors.Yellow
    char = "O"
    produces = "onion"


@register_object_type("tomato_stack", scope="overcooked")
class TomatoStack(_BaseStack):
    """An infinite pile of tomatoes."""

    color = constants.Colors.Red
    char = "T"
    produces = "tomato"


@register_object_type("pot", scope="overcooked")
class Pot(grid_object.GridObj):
    """A cooking pot that accepts ingredients and produces soup.

    Behavior is fully auto-generated by the Container + Recipe system.
    The ``container`` and ``recipes`` class attrs replace ~180 lines of
    classmethods + config.py functions.
    """

    color = constants.Colors.Grey
    char = "U"

    container = Container(capacity=3, pickup_requires="plate")
    recipes = [
        Recipe(["onion", "onion", "onion"], result="onion_soup", cook_time=30, reward=1.0),
        Recipe(["tomato", "tomato", "tomato"], result="tomato_soup", cook_time=30, reward=1.0),
    ]

    capacity: int = 3  # backward compat alias for container.capacity

    def __init__(self, state: int = 0, **kwargs):
        """Initialize pot with empty contents and default timer."""
        super().__init__(state=state)
        self.objects_in_pot: list[grid_object.GridObj] = []
        self.cooking_timer: int = 30

    def render(self, tile_img):
        """Draw pot circle with ingredient dots and timer text."""
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.5), self.color)

        for i, grid_obj in enumerate(self.objects_in_pot):
            fill_coords(
                tile_img,
                point_in_circle(cx=0.25 * (i + 1), cy=0.2, r=0.2),
                grid_obj.color,
            )

        if len(self.objects_in_pot) == self.capacity:
            add_text_to_image(tile_img, text=str(self.cooking_timer), position=(50, 75))

    def encode(self, encode_char: bool = True, scope: str = "global"):
        """Allow encoding to account for the type of soup in the pot."""
        char, _, state = super().encode(encode_char=encode_char, scope=scope)
        extra_state_encoding = int(any(isinstance(obj, Tomato) for obj in self.objects_in_pot))
        return (char, extra_state_encoding, state)


@register_object_type("onion_soup_stack", scope="overcooked")
class OnionSoupStack(_BaseStack):
    """An infinite dispenser of pre-made onion soups."""

    color = constants.Colors.LightBrown
    char = "1"
    produces = "onion_soup"


@register_object_type("tomato_soup_stack", scope="overcooked")
class TomatoSoupStack(_BaseStack):
    """An infinite dispenser of pre-made tomato soups."""

    color = constants.Colors.Red
    char = "2"
    produces = "tomato_soup"


@register_object_type("plate_stack", scope="overcooked")
class PlateStack(_BaseStack):
    """An infinite stack of plates for picking up soup."""

    color = constants.Colors.White
    char = "="
    produces = "plate"


def make_ingredient_and_stack(
    ingredient_name: str,
    ingredient_char: str,
    ingredient_color,
    stack_name: str,
    stack_char: str,
    scope: str = "overcooked",
) -> tuple:
    """Create and register a new ingredient + stack pair at runtime.

    Must be called BEFORE ``_build_interaction_tables`` runs (i.e. before
    env autowire). Typical call sites: at module-import time in a custom
    env module, or during env config setup before ``build_overcooked_extra_state``.

    Parameters
    ----------
    ingredient_name : str
        Object ID for the ingredient (e.g. "mushroom").
    ingredient_char : str
        Single character for grid encoding. Must be unique within *scope*.
    ingredient_color : tuple or list
        RGB color for rendering.
    stack_name : str
        Object ID for the stack (e.g. "mushroom_stack").
    stack_char : str
        Single character for grid encoding. Must be unique within *scope*.
    scope : str
        Registry scope (default "overcooked").

    Returns:
    -------
    tuple
        (IngredientCls, StackCls) -- the two newly created and registered classes.
    """
    # --- Create ingredient class ---
    IngredientCls = type(
        ingredient_name.title().replace("_", ""),
        (grid_object.GridObj,),
        {
            "color": ingredient_color,
            "char": ingredient_char,
            "can_pickup": when(),
        },
    )

    def _ingredient_init(self, *args, **kwargs):
        grid_object.GridObj.__init__(self, state=0)

    def _ingredient_render(self, tile_img):
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.3), self.color)

    IngredientCls.__init__ = _ingredient_init
    IngredientCls.render = _ingredient_render

    register_object_type(ingredient_name, scope=scope)(IngredientCls)

    # --- Create stack class (inherits can_pickup_from from _BaseStack) ---
    StackCls = type(
        stack_name.title().replace("_", ""),
        (_BaseStack,),
        {
            "color": ingredient_color,
            "char": stack_char,
            "produces": ingredient_name,
        },
    )

    register_object_type(stack_name, scope=scope)(StackCls)

    return (IngredientCls, StackCls)


@register_object_type("plate", scope="overcooked")
class Plate(grid_object.GridObj):
    """A plate used to serve completed soups."""

    color = constants.Colors.White
    char = "P"
    can_pickup = when()

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)

    def render(self, tile_img):
        """Draw a white circle."""
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.5), self.color)


@register_object_type("delivery_zone", scope="overcooked")
class DeliveryZone(grid_object.GridObj):
    """A zone where agents deliver completed soups for reward."""

    color = constants.Colors.Green
    char = "@"
    can_place_on = when(agent_holding=["onion_soup", "tomato_soup"])
    consumes_on_place = True

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)


@register_object_type("onion_soup", scope="overcooked")
class OnionSoup(grid_object.GridObj):
    """A completed onion soup, ready for delivery."""

    color = constants.Colors.LightBrown
    char = "S"
    can_pickup = when()

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)

    def render(self, tile_img):
        """Draw a plate with soup inside."""
        # Draw plate
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.5), Plate.color)

        # draw soup inside plate
        fill_coords(
            tile_img,
            point_in_circle(cx=0.5, cy=0.5, r=0.3),
            self.color,
        )


@register_object_type("tomato_soup", scope="overcooked")
class TomatoSoup(grid_object.GridObj):
    """A completed tomato soup, ready for delivery."""

    color = constants.Colors.Red
    char = "!"
    can_pickup = when()

    def __init__(self, *args, **kwargs):
        """Initialize with default state."""
        super().__init__(state=0)

    def render(self, tile_img):
        """Draw a plate with soup inside."""
        # Draw plate
        fill_coords(tile_img, point_in_circle(cx=0.5, cy=0.5, r=0.5), Plate.color)

        # draw soup inside plate
        fill_coords(
            tile_img,
            point_in_circle(cx=0.5, cy=0.5, r=0.3),
            self.color,
        )
