package thb.jeanluc.adventure.model;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import thb.jeanluc.adventure.model.item.Item;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;

/**
 * A location in the game world. Rooms are connected to neighbouring rooms
 * through {@link Direction}-keyed exits, and may contain items and NPCs
 * that the player can interact with.
 * <p>
 * Identity, name, and description are fixed after construction; the
 * exits, items, and NPCs collections are mutable to support the player
 * picking up items, dropping them, and the world otherwise evolving.
 * <p>
 * Mutation should go through the convenience methods on this class so
 * that ids stay consistent with the contained objects.
 */
@Getter
@RequiredArgsConstructor
public class Room {

    /** Unique identifier of this room within the world. */
    private final String id;

    /** Human-readable display name shown to the player. */
    private final String name;

    /** Description shown when the player enters the room or types {@code look}. */
    private final String description;

    /** Outgoing exits from this room, keyed by direction. */
    private final EnumMap<Direction, Room> exits = new EnumMap<>(Direction.class);

    /** Items currently in this room, keyed by item id. Insertion-ordered for stable display. */
    private final LinkedHashMap<String, Item> items = new LinkedHashMap<>();

    /** NPCs currently in this room, keyed by npc id. Insertion-ordered for stable display. */
    private final LinkedHashMap<String, Npc> npcs = new LinkedHashMap<>();

    /** Optional condition-gates per exit direction. */
    private final EnumMap<Direction, ExitLock> exitLocks = new EnumMap<>(Direction.class);

    /** Optional condition-gated description variants, first match wins. */
    private final List<DescriptionState> descriptionStates = new ArrayList<>();

    /** Whether this room is dark (needs a light source to enter/see). */
    @Setter
    private boolean dark;

    /** Optional GUI background-music track filename for this room; null = none. */
    @Setter
    private String music;

    /**
     * Connects this room to another in the given direction. Does not
     * create the reverse connection — callers must set that up
     * explicitly if a bidirectional path is desired.
     *
     * @param direction the direction in which the neighbour lies
     * @param neighbour the room reachable from here in that direction
     */
    public void addExit(Direction direction, Room neighbour) {
        exits.put(direction, neighbour);
    }

    /**
     * Adds a condition-gate to the exit in the given direction.
     *
     * @param direction the gated direction
     * @param lock      the lock to apply
     */
    public void addExitLock(Direction direction, ExitLock lock) {
        exitLocks.put(direction, lock);
    }

    /**
     * Adds a condition-gated description variant (first match wins).
     *
     * @param state the description variant
     */
    public void addDescriptionState(DescriptionState state) {
        descriptionStates.add(state);
    }

    /**
     * Looks up the room reachable in the given direction.
     *
     * @param direction the direction to query
     * @return the connected room, or empty if there is no exit that way
     */
    public Optional<Room> getExit(Direction direction) {
        return Optional.ofNullable(exits.get(direction));
    }

    /**
     * Places an item in this room.
     *
     * @param item the item to add; its id is used as the map key
     */
    public void addItem(Item item) {
        items.put(item.getId(), item);
    }

    /**
     * Removes an item from this room by id.
     *
     * @param itemId id of the item to remove
     * @return the removed item, or empty if no such item was present
     */
    public Optional<Item> removeItem(String itemId) {
        return Optional.ofNullable(items.remove(itemId));
    }

    /**
     * Looks up an item in this room without removing it.
     *
     * @param itemId id of the item to find
     * @return the item, or empty if no such item is present
     */
    public Optional<Item> findItem(String itemId) {
        return Optional.ofNullable(items.get(itemId));
    }

    /**
     * Places an NPC in this room.
     *
     * @param npc the NPC to add; its id is used as the map key
     */
    public void addNpc(Npc npc) {
        npcs.put(npc.getId(), npc);
    }

    /**
     * Looks up an NPC in this room.
     *
     * @param npcId id of the NPC to find
     * @return the NPC, or empty if no such NPC is present
     */
    public Optional<Npc> findNpc(String npcId) {
        return Optional.ofNullable(npcs.get(npcId));
    }
}
