package thb.jeanluc.adventure.loader;

import lombok.RequiredArgsConstructor;
import thb.jeanluc.adventure.loader.dto.ConditionDto;
import thb.jeanluc.adventure.loader.dto.DescriptionStateDto;
import thb.jeanluc.adventure.loader.dto.DialogueLineDto;
import thb.jeanluc.adventure.loader.dto.EffectDto;
import thb.jeanluc.adventure.loader.dto.ExitLockDto;
import thb.jeanluc.adventure.loader.dto.NpcDto;
import thb.jeanluc.adventure.loader.dto.ReactionDto;
import thb.jeanluc.adventure.loader.dto.RoomDto;
import thb.jeanluc.adventure.model.DescriptionState;
import thb.jeanluc.adventure.model.DialogueLine;
import thb.jeanluc.adventure.model.Direction;
import thb.jeanluc.adventure.model.ExitLock;
import thb.jeanluc.adventure.model.Npc;
import thb.jeanluc.adventure.model.NpcReaction;
import thb.jeanluc.adventure.model.Room;
import thb.jeanluc.adventure.model.item.Item;

import java.util.List;
import java.util.Map;

/**
 * Phase 3 of the loading pipeline. Walks DTOs once more and resolves all
 * string ids to actual domain references on the prebuilt registries.
 */
@RequiredArgsConstructor
public class ReferenceResolver {

    /** Global item registry, prebuilt by phase 2. */
    private final Map<String, Item> items;

    /** Global NPC registry, prebuilt by phase 2. */
    private final Map<String, Npc> npcs;

    /** Global room registry, prebuilt by phase 2. */
    private final Map<String, Room> rooms;

    /**
     * Wires exits, items, and NPCs into every room based on its DTO.
     *
     * @param roomDtos all room DTOs as read from YAML
     * @throws WorldLoadException on any unresolved id or unparsable direction
     */
    public void resolveRooms(List<RoomDto> roomDtos) {
        for (RoomDto dto : roomDtos) {
            Room room = rooms.get(dto.id());
            if (dto.exits() != null) {
                for (Map.Entry<String, String> e : dto.exits().entrySet()) {
                    Direction direction;
                    try {
                        direction = Direction.fromString(e.getKey());
                    } catch (IllegalArgumentException ex) {
                        throw new WorldLoadException(
                                "Room '" + dto.id() + "' has unknown exit direction '" + e.getKey() + "'");
                    }
                    Room target = rooms.get(e.getValue());
                    if (target == null) {
                        throw new WorldLoadException(
                                "Room '" + dto.id() + "' exits to unknown room '" + e.getValue() + "'");
                    }
                    room.addExit(direction, target);
                }
            }
            if (dto.items() != null) {
                for (String itemId : dto.items()) {
                    Item item = items.get(itemId);
                    if (item == null) {
                        throw new WorldLoadException(
                                "Room '" + dto.id() + "' references unknown item '" + itemId + "'");
                    }
                    room.addItem(item);
                }
            }
            if (dto.npcs() != null) {
                for (String npcId : dto.npcs()) {
                    Npc npc = npcs.get(npcId);
                    if (npc == null) {
                        throw new WorldLoadException(
                                "Room '" + dto.id() + "' references unknown npc '" + npcId + "'");
                    }
                    room.addNpc(npc);
                }
            }
            if (dto.exitLocks() != null) {
                for (ExitLockDto lock : dto.exitLocks()) {
                    Direction direction;
                    try {
                        direction = Direction.fromString(lock.direction());
                    } catch (IllegalArgumentException ex) {
                        throw new WorldLoadException(
                                "Room '" + dto.id() + "' has exit lock with unknown direction '" + lock.direction() + "'");
                    }
                    room.addExitLock(direction, new ExitLock(
                            ConditionDto.toModelList(lock.requires()), lock.blocked()));
                }
            }
            if (dto.descriptionStates() != null) {
                for (DescriptionStateDto ds : dto.descriptionStates()) {
                    room.addDescriptionState(new DescriptionState(
                            ConditionDto.toModelList(ds.when()), ds.text()));
                }
            }
        }
    }

    /**
     * Wires every NPC's reactions to actual item references.
     *
     * @param npcDtos all NPC DTOs as read from YAML
     * @throws WorldLoadException on any unresolved id or duplicate trigger
     */
    public void resolveNpcs(List<NpcDto> npcDtos) {
        for (NpcDto dto : npcDtos) {
            Npc npc = npcs.get(dto.id());
            if (dto.dialogue() != null) {
                for (DialogueLineDto dl : dto.dialogue()) {
                    npc.addDialogue(new DialogueLine(ConditionDto.toModelList(dl.when()), dl.text()));
                }
            }
            if (dto.reactions() == null) {
                continue;
            }
            for (ReactionDto r : dto.reactions()) {
                if (r.onReceive() == null) {
                    throw new WorldLoadException(
                            "NPC '" + dto.id() + "' has a reaction without onReceive");
                }
                if (!items.containsKey(r.onReceive())) {
                    throw new WorldLoadException(
                            "NPC '" + dto.id() + "' reacts to unknown item '" + r.onReceive() + "'");
                }
                if (npc.getReactions().containsKey(r.onReceive())) {
                    throw new WorldLoadException(
                            "NPC '" + dto.id() + "' has duplicate reaction for item '" + r.onReceive() + "'");
                }
                Item gives = null;
                if (r.gives() != null) {
                    gives = items.get(r.gives());
                    if (gives == null) {
                        throw new WorldLoadException(
                                "NPC '" + dto.id() + "' gives unknown item '" + r.gives() + "'");
                    }
                }
                Item consumes = null;
                if (r.consumes() != null) {
                    consumes = items.get(r.consumes());
                    if (consumes == null) {
                        throw new WorldLoadException(
                                "NPC '" + dto.id() + "' consumes unknown item '" + r.consumes() + "'");
                    }
                }
                NpcReaction reaction = NpcReaction.builder()
                        .consumes(consumes)
                        .gives(gives)
                        .response(r.response())
                        .requires(ConditionDto.toModelList(r.requires()))
                        .effects(EffectDto.toModelList(r.effects()))
                        .build();
                npc.putReaction(r.onReceive(), reaction);
            }
        }
    }
}
