"""Look command for viewing the world."""

from mudlib.commands import CommandDefinition, register
from mudlib.commands.things import _format_thing_name
from mudlib.effects import get_effects_at
from mudlib.entity import Entity
from mudlib.gametime import get_game_day, get_game_hour
from mudlib.player import Player
from mudlib.render.ansi import RESET, colorize_terrain
from mudlib.render.room import (
    render_atmosphere,
    render_entity_lines,
    render_exits,
    render_location,
    render_nearby,
    render_where,
)
from mudlib.seasons import get_season
from mudlib.thing import Thing
from mudlib.visibility import get_visibility
from mudlib.weather import (
    WeatherCondition,
    get_current_weather,
    get_weather_description,
)
from mudlib.zone import Zone

# Viewport dimensions
VIEWPORT_WIDTH = 21
VIEWPORT_HEIGHT = 11


async def cmd_look(player: Player, args: str) -> None:
    """Render the current viewport to the player.

    Args:
        player: The player executing the command
        args: Command arguments (if provided, use targeting to resolve)
    """
    # If args provided, use targeting to resolve
    if args.strip():
        from mudlib.targeting import (
            find_entity_on_tile,
            find_in_inventory,
            find_thing_on_tile,
        )

        target_name = args.strip()

        # First try to find an entity on the tile
        entity = find_entity_on_tile(target_name, player)
        if entity:
            # Show entity info (name and posture)
            if hasattr(entity, "description") and entity.description:
                await player.send(f"{entity.description}\r\n")
            else:
                await player.send(f"{entity.name} is {entity.posture}.\r\n")
            return

        # Then try to find a thing on the ground
        zone = player.location
        if zone is not None and isinstance(zone, Zone):
            thing = find_thing_on_tile(target_name, zone, player.x, player.y)
            if thing:
                # Show thing description
                desc = getattr(thing, "description", "")
                if desc:
                    await player.send(f"{desc}\r\n")
                else:
                    await player.send("You see nothing special.\r\n")
                return

        # Finally try inventory
        thing = find_in_inventory(target_name, player)
        if thing:
            # Show thing description
            desc = getattr(thing, "description", "")
            if desc:
                await player.send(f"{desc}\r\n")
            else:
                await player.send("You see nothing special.\r\n")
            return

        # Nothing found
        await player.send("You don't see that here.\r\n")
        return

    zone = player.location
    if zone is None or not isinstance(zone, Zone):
        player.writer.write("You are nowhere.\r\n")
        await player.writer.drain()
        return

    # Compute environment state once
    hour = None
    day = None
    weather = None
    season = None
    try:
        hour = get_game_hour()
        day = get_game_day()
        weather = get_current_weather()
        season = get_season(day)
    except RuntimeError:
        pass

    # Use hour/weather for visibility
    if hour is not None and weather is not None:
        effective_width, effective_height = get_visibility(
            hour, weather, VIEWPORT_WIDTH, VIEWPORT_HEIGHT
        )
    else:
        effective_width = VIEWPORT_WIDTH
        effective_height = VIEWPORT_HEIGHT

    # Get the viewport from the zone
    viewport = zone.get_viewport(player.x, player.y, effective_width, effective_height)

    # Calculate center position
    center_x = effective_width // 2
    center_y = effective_height // 2

    # Get nearby entities (players and mobs) from the zone
    # Viewport half-diagonal distance for range
    viewport_range = effective_width // 2 + effective_height // 2
    nearby = zone.contents_near(player.x, player.y, viewport_range)

    # Build a list of (relative_x, relative_y) for other entities
    entity_positions = []
    for obj in nearby:
        # Only show entities (players/mobs), not the current player
        if not isinstance(obj, Entity) or obj is player:
            continue

        # Skip dead mobs
        if hasattr(obj, "alive") and not obj.alive:
            continue

        # Calculate relative position (shortest path wrapping)
        dx = obj.x - player.x
        dy = obj.y - player.y
        if zone.toroidal:
            if dx > zone.width // 2:
                dx -= zone.width
            elif dx < -(zone.width // 2):
                dx += zone.width
            if dy > zone.height // 2:
                dy -= zone.height
            elif dy < -(zone.height // 2):
                dy += zone.height
        rel_x = dx + center_x
        rel_y = dy + center_y

        # Check if within viewport bounds
        if 0 <= rel_x < effective_width and 0 <= rel_y < effective_height:
            entity_positions.append((rel_x, rel_y))

    # Build the output with ANSI coloring
    # priority: player @ > other players * > mobs * > effects > terrain
    half_width = effective_width // 2
    half_height = effective_height // 2

    output_lines = []
    for y, row in enumerate(viewport):
        line = []
        for x, tile in enumerate(row):
            # Check if this is the player's position
            if x == center_x and y == center_y:
                line.append(colorize_terrain("@", player.color_depth or "16"))
            # Check if this is another entity's position
            elif (x, y) in entity_positions:
                line.append(colorize_terrain("*", player.color_depth or "16"))
            else:
                # Check for active effects at this world position
                world_x, world_y = zone.wrap(
                    player.x - half_width + x,
                    player.y - half_height + y,
                )
                effects = get_effects_at(world_x, world_y)
                if effects:
                    # use the most recent effect
                    e = effects[-1]
                    line.append(f"{e.color}{e.char}{RESET}")
                else:
                    line.append(colorize_terrain(tile, player.color_depth or "16"))
        output_lines.append("".join(line))

    # Build structured output
    output = []

    # Where header
    output.append(render_where(zone.description))

    # Atmosphere line (between Where and viewport)
    if hour is not None and weather is not None and season is not None:
        if weather.condition == WeatherCondition.clear:
            weather_desc = ""
        else:
            weather_desc = get_weather_description(weather)

        atmosphere = render_atmosphere(hour, weather_desc, season)
        output.append(atmosphere)

    # Viewport
    output.append("\r\n".join(output_lines))

    # Location line
    output.append(render_location(zone, player.x, player.y))

    # Collect nearby entities (in viewport but not on player's tile)
    nearby_entities = [
        obj
        for obj in nearby
        if isinstance(obj, Entity)
        and obj is not player
        and (not hasattr(obj, "alive") or obj.alive)
        and not (obj.x == player.x and obj.y == player.y)
    ]
    if nearby_entities:
        output.append(render_nearby(nearby_entities, player))

    # Exits line
    output.append(render_exits(zone, player.x, player.y))

    # Send to player
    player.writer.write("\r\n".join(output) + "\r\n")

    # Blank line before entity/item details
    player.writer.write("\r\n")

    # Show entities (mobs, other players) at the player's position
    entities_here = [
        obj
        for obj in zone.contents_at(player.x, player.y)
        if isinstance(obj, Entity)
        and obj is not player
        and (not hasattr(obj, "alive") or obj.alive)
    ]
    if entities_here:
        entity_lines = render_entity_lines(entities_here, player)
        # Convert \n to \r\n for telnet
        player.writer.write(entity_lines.replace("\n", "\r\n") + "\r\n")

    # Show items on the ground at player's position
    from mudlib.corpse import Corpse
    from mudlib.portal import Portal

    contents_here = zone.contents_at(player.x, player.y)
    corpses = [obj for obj in contents_here if isinstance(obj, Corpse)]
    ground_items = [
        obj
        for obj in contents_here
        if isinstance(obj, Thing)
        and not isinstance(obj, Portal)
        and not isinstance(obj, Corpse)
    ]
    portals = [obj for obj in contents_here if isinstance(obj, Portal)]

    if corpses:
        for corpse in corpses:
            player.writer.write(f"{corpse.name} is here.\r\n")

    if ground_items:
        names = ", ".join(_format_thing_name(item) for item in ground_items)
        player.writer.write(f"On the ground: {names}\r\n")

    if portals:
        for portal in portals:
            player.writer.write(f"You see {portal.name}.\r\n")

    await player.writer.drain()


# Register the look command with its alias
register(CommandDefinition("look", cmd_look, aliases=["l"]))
