package thb.jeanluc.adventure.command.impl;

import thb.jeanluc.adventure.command.Command;
import thb.jeanluc.adventure.game.Conditions;
import thb.jeanluc.adventure.game.GameContext;
import thb.jeanluc.adventure.game.Light;
import thb.jeanluc.adventure.game.Pathfinder;
import thb.jeanluc.adventure.map.MapLayout;
import thb.jeanluc.adventure.model.Direction;
import thb.jeanluc.adventure.model.ExitLock;
import thb.jeanluc.adventure.model.Room;

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

/**
 * Moves the player. {@code go <direction>} steps once to a connected room.
 * {@code go to <room>} auto-walks the shortest path (BFS) over already-visited
 * rooms, honouring locked exits and darkness. Usage: {@code go <direction>} or
 * {@code go to <room>}. The parser strips the filler word "to", so "go to
 * &lt;room&gt;" and "go &lt;room&gt;" are equivalent.
 */
public class GoCommand implements Command {

    @Override
    public void execute(GameContext ctx, List<String> args) {
        if (args.isEmpty()) {
            ctx.getIo().write("Go where? Try 'go north' or 'go to <room>'.");
            return;
        }
        try {
            Direction dir = Direction.fromString(args.getFirst());
            moveDirection(ctx, dir);
        } catch (IllegalArgumentException notADirection) {
            goToRoom(ctx, args);
        }
    }

    /** Single-step move in a compass direction (the original behaviour). */
    private void moveDirection(GameContext ctx, Direction dir) {
        Room here = ctx.getPlayer().getCurrentRoom();
        Optional<Room> next = here.getExit(dir);
        if (next.isEmpty()) {
            ctx.getIo().write("You can't go " + dir.getLabel() + " from here.");
            return;
        }
        ExitLock lock = here.getExitLocks().get(dir);
        if (lock != null && !Conditions.all(lock.requires(), ctx)) {
            ctx.getIo().write(lock.blocked());
            return;
        }
        if (!Light.isLit(ctx, next.get())) {
            ctx.getIo().write("It's pitch black beyond the doorway — you need a lit light source "
                    + "(try lighting your lamp with 'use lamp').");
            return;
        }
        ctx.getPlayer().setCurrentRoom(next.get());
        new LookCommand().execute(ctx, List.of());
    }

    /** Resolve a visited room by id/name and auto-walk the BFS path to it. */
    private void goToRoom(GameContext ctx, List<String> args) {
        String name = String.join(" ", args).toLowerCase();
        Room target = resolveVisited(ctx, name);
        if (target == null) {
            ctx.getIo().write("You don't know any direction or place called '" + name + "'.");
            return;
        }
        Room start = ctx.getPlayer().getCurrentRoom();
        if (target == start) {
            ctx.getIo().write("You're already in the " + target.getName() + ".");
            return;
        }
        List<Room> path = Pathfinder.findPath(ctx, target);
        if (path.isEmpty()) {
            ctx.getIo().write("You can't find a way there right now.");
            return;
        }
        List<Room> route = new ArrayList<>();
        route.add(start);
        route.addAll(path);
        for (Room step : path) {
            ctx.getPlayer().setCurrentRoom(step);
            ctx.getIo().travelStep(MapLayout.compute(
                    ctx.getWorld(), ctx.getPlayer().getVisitedRoomIds(), step));
        }
        ctx.getIo().write(summary(route));
        new LookCommand().execute(ctx, List.of());
    }

    /** Finds a visited room whose id or display name matches {@code name}. */
    private Room resolveVisited(GameContext ctx, String name) {
        // the command test harness (CommandTestSupport) uses a null world; production always has one
        if (ctx.getWorld() == null) {
            return null;
        }
        for (String id : ctx.getPlayer().getVisitedRoomIds()) {
            Room r = ctx.getWorld().getRooms().get(id);
            if (r == null) {
                continue;
            }
            if (id.equalsIgnoreCase(name) || r.getName().equalsIgnoreCase(name)) {
                return r;
            }
        }
        return null;
    }

    /** Names start, destination, and at most one middle waypoint. */
    private String summary(List<Room> route) {
        int n = route.size();
        String start = route.get(0).getName();
        String dest = route.get(n - 1).getName();
        if (n >= 3) {
            String middle = route.get(n / 2).getName();
            return "You make your way from the " + start + ", through the " + middle
                    + ", to the " + dest + ".";
        }
        return "You make your way from the " + start + " to the " + dest + ".";
    }

    @Override
    public String help() {
        return "go <direction>  -  move one room (north/south/east/west); "
                + "or  go to <room>  to travel to a known room";
    }
}
