//
// Created by sebastian on 05.08.26.
//

#include "PiecePathBuilder.h"

#include <format>

#include "ludo/LudoGameMode.h"
#include "LudoRules.h"

namespace {
    int stateOrder(ludo::PieceState state) {
        switch (state) {
            case ludo::PieceState::AtHome:        return 0;
            case ludo::PieceState::OnTrack:       return 1;
            case ludo::PieceState::InHomeStretch: return 2;
            case ludo::PieceState::Finished:      return 3;
        }
        throw std::invalid_argument("computePath: unbekannter PieceState");
    }
}

std::vector<PathNode> ludo::PiecePathBuilder::computePath(PlayerColor color, PieceState startingState,
                                                                int startPosition, PieceState targetState, int targetPosition) {
        auto errorContext = [&]() {
        return std::format(
            "color={}, startState={}, startPosition={}, targetState={}, targetPosition={}, "
            "kTrackLength={}, kHomeStretchLength={}",
            toString(color),
            toString(startingState),
            startPosition,
            toString(targetState),
            targetPosition,
            kTrackLength,
            ludo::kHomeStretchLength
        );
    };

    if (startingState == targetState && startPosition == targetPosition) {
        return {};
    }

    // --- Sonderfall: Figur wird geschlagen und springt direkt zurück ---
    if (targetState == PieceState::AtHome) {
        if (startingState != PieceState::OnTrack) {
            throw std::invalid_argument(
                std::format(
                    "computePath: targetState==AtHome ist nur von OnTrack aus erlaubt (Capture). {}",
                    errorContext()
                )
            );
        }

        return {
            PathNode{startingState, startPosition},
            PathNode{targetState, targetPosition}
        };
    }


    // --- Nur Vorwärtsbewegungen sind erlaubt ---
    if (stateOrder(targetState) < stateOrder(startingState)) {
        throw std::invalid_argument(
            std::format(
                "computePath: targetState liegt vor startingState (Rueckwaerts nicht unterstuetzt). "
                "stateOrder(start)={}, stateOrder(target)={}. {}",
                stateOrder(startingState),
                stateOrder(targetState),
                errorContext()
            )
        );
    }


    if (startingState == PieceState::Finished) {
        throw std::invalid_argument(
            std::format(
                "computePath: startingState ist bereits Finished, aber Ziel weicht ab. {}",
                errorContext()
            )
        );
    }


    if (startingState == PieceState::InHomeStretch &&
        targetState == PieceState::InHomeStretch &&
        targetPosition < startPosition) {

        throw std::invalid_argument(
            std::format(
                "computePath: targetPosition liegt im HomeStretch vor startPosition "
                "(Rueckwaertsbewegung). {}",
                errorContext()
            )
        );
    }


    // --- Positionsbereiche ---
    if (targetState == PieceState::OnTrack &&
        (targetPosition < 0 || targetPosition >= kTrackLength)) {

        throw std::invalid_argument(
            std::format(
                "computePath: targetPosition ausserhalb OnTrack-Bereich. "
                "Erwartet [0,{}), erhalten {}. {}",
                kTrackLength,
                targetPosition,
                errorContext()
            )
        );
    }


    if (targetState == PieceState::InHomeStretch &&
        (targetPosition < 0 || targetPosition >= kHomeStretchLength)) {

        throw std::invalid_argument(
            std::format(
                "computePath: targetPosition ausserhalb InHomeStretch-Bereich. "
                "Erwartet [0,{}), erhalten {}. {}",
                kHomeStretchLength,
                targetPosition,
                errorContext()
            )
        );
    }


    if (targetState == PieceState::Finished &&
        targetPosition != kHomeStretchLength - 1) {

        throw std::invalid_argument(
            std::format(
                "computePath: targetPosition fuer Finished ungueltig. "
                "Erwartet {}, erhalten {}. {}",
                kHomeStretchLength - 1,
                targetPosition,
                errorContext()
            )
        );
    }


    if (startingState == PieceState::OnTrack &&
        (startPosition < 0 || startPosition >= kTrackLength)) {

        throw std::invalid_argument(
            std::format(
                "computePath: startPosition ausserhalb OnTrack-Bereich. "
                "Erwartet [0,{}), erhalten {}. {}",
                kTrackLength,
                startPosition,
                errorContext()
            )
        );
    }


    if (startingState == PieceState::InHomeStretch &&
        (startPosition < 0 || startPosition >= kHomeStretchLength)) {

        throw std::invalid_argument(
            std::format(
                "computePath: startPosition ausserhalb InHomeStretch-Bereich. "
                "Erwartet [0,{}), erhalten {}. {}",
                kHomeStretchLength,
                startPosition,
                errorContext()
            )
        );
    }

    std::vector<PathNode> path;

    if (startPosition == targetPosition && startingState == targetState) return path;

    PieceState state = startingState;
    int position = startPosition;

    path.push_back({state, position});

    // Defense in depth: falls die Validierung oben eine Kombination übersieht
    // (z.B. nach zukünftigen Erweiterungen), lieber laut abstürzen als RAM fressen.
    const int maxSteps = kTrackLength + kHomeStretchLength + 4;
    int steps = 0;

    while (state != targetState || position != targetPosition) {
        if (++steps > maxSteps) {
            throw std::logic_error(
                "computePath: Iterationslimit ueberschritten - unreachable Ziel trotz Validierung");
        }

        switch (state) {
            case PieceState::AtHome:
                state = PieceState::OnTrack;
                position = LudoRules::getEntryOffset(color);
                break;
            case PieceState::OnTrack:
                if (position == LudoRules::getEntryOffset(color) && (targetState == PieceState::InHomeStretch || targetState == PieceState::Finished)) {
                    state = PieceState::InHomeStretch;
                    position = 0;
                }  else {
                    position = (position + 1) % kTrackLength;
                }
                break;
            case PieceState::InHomeStretch:
                if (targetState == PieceState::Finished && position == kHomeStretchLength - 1) {
                    state = PieceState::Finished;
                } else {
                    ++position;
                }
                break;
            case PieceState::Finished:
                break;
        }
        path.push_back({state, position});
    }
    return path;
}
