package thb.jeanluc.adventure.model;

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

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;

/**
 * The player avatar. Tracks the current room, an ordered inventory, and a
 * gold counter. Mutated by the various command implementations.
 */
@Getter
public class Player {

    /** Room the player is currently standing in. */
    private Room currentRoom;

    /** Ids of rooms the player has entered, in first-visit order (for the map). */
    private final Set<String> visitedRoomIds = new LinkedHashSet<>();

    /**
     * Inventory keyed by item id. {@link LinkedHashMap} keeps insertion
     * order for stable {@code inventory} listings while still providing
     * O(1) lookup on {@code drop}/{@code use}.
     */
    private final LinkedHashMap<String, Item> inventory = new LinkedHashMap<>();

    /** Gold currency. Currently informational; no commands spend it yet. */
    @Setter
    private int gold;

    /**
     * Creates a player at the given starting room with the given starting gold.
     *
     * @param startRoom room the player starts in; must not be null
     * @param startGold initial gold amount; must be non-negative
     */
    public Player(Room startRoom, int startGold) {
        if (startRoom == null) {
            throw new IllegalArgumentException("startRoom must not be null");
        }
        if (startGold < 0) {
            throw new IllegalArgumentException("startGold must not be negative");
        }
        this.currentRoom = startRoom;
        this.gold = startGold;
        this.visitedRoomIds.add(startRoom.getId());
    }

    /**
     * Moves the player into a room and records it as visited.
     *
     * @param room the room now occupied; must not be null
     */
    public void setCurrentRoom(Room room) {
        this.currentRoom = room;
        this.visitedRoomIds.add(room.getId());
    }

    /**
     * Places an item into the inventory.
     *
     * @param item the item to add
     */
    public void addItem(Item item) {
        inventory.put(item.getId(), item);
    }

    /**
     * Removes and returns an item from the inventory.
     *
     * @param itemId id of the item to remove
     * @return the removed item, or empty if it was not held
     */
    public Optional<Item> removeItem(String itemId) {
        return Optional.ofNullable(inventory.remove(itemId));
    }

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

    /**
     * Convenience predicate for inventory containment.
     *
     * @param itemId id of the item to test
     * @return {@code true} if the player holds the item
     */
    public boolean hasItem(String itemId) {
        return inventory.containsKey(itemId);
    }
}
