#pragma once

#include <array>
#include <optional>
#include <random>
#include <functional>
#include <vector>
#include <cstdint>
#include <SDL3/SDL.h>

#include "../core/Game.h" // For PieceType enums and gravity table helpers

// Cooperative two-player session with a shared 20-column board split into halves.
// This is an early scaffold: rules and rendering hooks will be iterated in follow-up passes.
class CoopGame {
public:
    enum class PlayerSide { Left, Right };

    static constexpr int COLS = 20;
    static constexpr int ROWS = Game::ROWS;
    static constexpr int TILE = Game::TILE;

    struct Piece { PieceType type{PIECE_COUNT}; int rot{0}; int x{0}; int y{-2}; };

    struct Cell {
        int value{0};              // 0 empty else color index (1..7)
        PlayerSide owner{PlayerSide::Left};
        bool occupied{false};
    };

    struct RowHalfState {
        bool leftFull{false};
        bool rightFull{false};
    };

    struct PlayerState {
        PlayerSide side{PlayerSide::Left};
        Piece cur{};
        Piece hold{};
        Piece next{};
        uint64_t pieceSeq{0};
        bool canHold{true};
        bool softDropping{false};
        bool toppedOut{false};
        double fallAcc{0.0};
        double lockAcc{0.0};
        int score{0};
        int lines{0};
        int level{0};
        int tetrisesMade{0};
        int currentCombo{0};
        int maxCombo{0};
        int comboCount{0};
        std::vector<PieceType> bag{}; // 7-bag queue
        std::mt19937 rng{ std::random_device{}() };
    };

    explicit CoopGame(int startLevel = 0);
    using SoundCallback = std::function<void(int)>;
    using LevelUpCallback = std::function<void(int)>;
    void setSoundCallback(SoundCallback cb) { soundCallback = cb; }
    void setLevelUpCallback(LevelUpCallback cb) { levelUpCallback = cb; }

    void reset(int startLevel = 0);
    void resetDeterministic(int startLevel, uint32_t seed);
    void tickGravity(double frameMs);
    void updateVisualEffects(double frameMs);

    // Determinism / desync detection
    uint64_t computeStateHash() const;

    // Per-player inputs -----------------------------------------------------
    void setSoftDropping(PlayerSide side, bool on);
    void move(PlayerSide side, int dx);
    void rotate(PlayerSide side, int dir); // +1 cw, -1 ccw
    void hardDrop(PlayerSide side);
    void holdCurrent(PlayerSide side);

    // Accessors -------------------------------------------------------------
    const std::array<Cell, COLS * ROWS>& boardRef() const { return board; }
    const Piece& current(PlayerSide s) const { return player(s).cur; }
    const Piece& next(PlayerSide s) const { return player(s).next; }
    const Piece& held(PlayerSide s) const { return player(s).hold; }
    bool canHold(PlayerSide s) const { return player(s).canHold; }
    bool isGameOver() const { return gameOver; }
    int score() const { return _score; }
    int score(PlayerSide s) const { return player(s).score; }
    int lines() const { return _lines; }
    int lines(PlayerSide s) const { return player(s).lines; }
    int level() const { return _level; }
    int level(PlayerSide s) const { return player(s).level; }
    int comboCount() const { return _comboCount; }
    int maxCombo() const { return _maxCombo; }
    int tetrisesMade() const { return _tetrisesMade; }
    int elapsed() const { return static_cast<int>(elapsedMs / 1000.0); }
    int elapsed(PlayerSide) const { return elapsed(); }
    int startLevelBase() const { return startLevel; }
    double getGravityMs() const { return gravityMs; }
    double getFallAccumulator(PlayerSide s) const { return player(s).fallAcc; }
    bool isSoftDropping(PlayerSide s) const { return player(s).softDropping; }
    uint64_t currentPieceSequence(PlayerSide s) const { return player(s).pieceSeq; }
    const std::vector<int>& getCompletedLines() const { return completedLines; }
    bool hasCompletedLines() const { return !completedLines.empty(); }
    void clearCompletedLines();
    const std::array<RowHalfState, ROWS>& rowHalfStates() const { return rowStates; }

    // Simple visual-effect compatibility (stubbed for now)
    bool hasHardDropShake() const { return hardDropShakeTimerMs > 0.0; }
    double hardDropShakeStrength() const;
    const std::vector<SDL_Point>& getHardDropCells() const { return hardDropCells; }
    uint32_t getHardDropFxId() const { return hardDropFxId; }

    static bool cellFilled(const Piece& p, int cx, int cy);

private:
    static constexpr double LOCK_DELAY_MS = 500.0;

    void resetInternal(int startLevel_, const std::optional<uint32_t>& seedOpt);

    std::array<Cell, COLS * ROWS> board{};
    std::array<RowHalfState, ROWS> rowStates{};
    PlayerState left{};
    PlayerState right{ PlayerSide::Right };

    int _score{0};
    int _lines{0};
    int _level{1};
    int _tetrisesMade{0};
    int _currentCombo{0};
    int _maxCombo{0};
    int _comboCount{0};
    int startLevel{0};
    double gravityMs{800.0};
    double gravityGlobalMultiplier{1.0};
    bool gameOver{false};

    double elapsedMs{0.0};

    std::vector<int> completedLines;

    // Impact FX
    double hardDropShakeTimerMs{0.0};
    static constexpr double HARD_DROP_SHAKE_DURATION_MS = 320.0;
    std::vector<SDL_Point> hardDropCells;
    uint32_t hardDropFxId{0};
    uint64_t pieceSequence{0};
    SoundCallback soundCallback;
    LevelUpCallback levelUpCallback;

    // Helpers ---------------------------------------------------------------
    PlayerState& player(PlayerSide s) { return s == PlayerSide::Left ? left : right; }
    const PlayerState& player(PlayerSide s) const { return s == PlayerSide::Left ? left : right; }

    void refillBag(PlayerState& ps);
    Piece drawFromBag(PlayerState& ps);
    void spawn(PlayerState& ps);
    bool collides(const PlayerState& ps, const Piece& p) const;
    bool tryMove(PlayerState& ps, int dx, int dy);
    void lock(PlayerState& ps);
    void findCompletedLines();
    void clearLinesInternal();
    void updateRowStates();
    void applyLineClearRewards(PlayerState& creditPlayer, int cleared);
    double gravityMsForLevel(int level) const;
    int columnMin(PlayerSide s) const { return s == PlayerSide::Left ? 0 : 10; }
    int columnMax(PlayerSide s) const { return s == PlayerSide::Left ? 9 : 19; }
};
