import Foundation

struct FreeCellRules: GameRules {
    let variant: GameVariant = .freeCell

    func deal(deck: [Card]) -> GameSnapshot {
        var remaining = deck
        var tableaus: [[Card]] = []

        // Deal all 52 cards to 8 tableaus, all face-up
        // First 4 tableaus get 7 cards, last 4 get 6 cards
        for i in 0..<8 {
            let cardCount = i < 4 ? 7 : 6
            var column: [Card] = []
            for _ in 0..<cardCount {
                var card = remaining.removeFirst()
                card.isFaceUp = true
                column.append(card)
            }
            tableaus.append(column)
        }

        return GameSnapshot(
            tableaus: tableaus,
            foundations: [[], [], [], []],
            stock: [],
            waste: [],
            freeCells: [nil, nil, nil, nil],
            moves: 0,
            score: 0
        )
    }

    func canMove(cards: [Card], from: CardLocation, to: CardLocation, state: GameSnapshot) -> Bool {
        guard let firstCard = cards.first else { return false }

        switch to {
        case .foundation(let index):
            guard cards.count == 1 else { return false }
            guard index >= 0, index < state.foundations.count else { return false }
            return MoveValidator.canPlaceOnFoundation(firstCard, topCard: state.foundations[index].last)

        case .tableau(let index):
            guard index >= 0, index < state.tableaus.count else { return false }
            let maxMovable = calculateMaxMovableCards(state: state, targetEmpty: state.tableaus[index].isEmpty)
            guard cards.count <= maxMovable else { return false }

            let tableau = state.tableaus[index]
            if tableau.isEmpty { return true }
            guard let topCard = tableau.last else { return false }
            return MoveValidator.isAlternatingColor(firstCard, with: topCard)
                && MoveValidator.isDescending(firstCard, onto: topCard)

        case .freeCell(let index):
            guard cards.count == 1 else { return false }
            guard index >= 0, index < state.freeCells.count else { return false }
            return state.freeCells[index] == nil

        default:
            return false
        }
    }

    func drawFromStock(state: inout GameSnapshot, drawCount: Int) -> MoveAction? {
        nil // FreeCell has no stock
    }

    func scoreForMove(from: CardLocation, to: CardLocation) -> Int {
        switch (from, to) {
        case (_, .foundation): return 10
        case (.freeCell, .tableau): return 5
        default: return 0
        }
    }

    func isWon(state: GameSnapshot) -> Bool {
        state.foundations.allSatisfy { $0.count == 13 }
    }

    func canAutoComplete(state: GameSnapshot) -> Bool {
        // All tableau cards must be face-up and in descending order
        let allFaceUp = state.tableaus.allSatisfy { $0.allSatisfy { $0.isFaceUp } }
        guard allFaceUp else { return false }

        // Check that all tableaus are in valid descending alternating-color order
        for tableau in state.tableaus {
            for i in 1..<tableau.count {
                guard MoveValidator.isAlternatingColor(tableau[i], with: tableau[i - 1]),
                      MoveValidator.isDescending(tableau[i], onto: tableau[i - 1]) else {
                    return false
                }
            }
        }
        return true
    }

    /// Power move formula: (1 + empty_freecells) × 2^empty_tableaus
    func calculateMaxMovableCards(state: GameSnapshot, targetEmpty: Bool) -> Int {
        let emptyFreeCells = state.freeCells.filter { $0 == nil }.count
        // Don't count the target tableau as empty if we're moving to an empty tableau
        let emptyTableaus = state.tableaus.filter { $0.isEmpty }.count - (targetEmpty ? 1 : 0)
        let adjustedEmptyTableaus = max(0, emptyTableaus)
        return (1 + emptyFreeCells) * Int(pow(2.0, Double(adjustedEmptyTableaus)))
    }

    func findHints(state: GameSnapshot, settings: DifficultySettings) -> [HintResult] {
        guard settings.hintsEnabled else { return [] }
        var hints: [HintResult] = []

        // Priority 1: Aces to foundation (from tableaus and free cells)
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            guard let topCard = tableau.last, MoveValidator.isAce(topCard) else { continue }
            for (fIndex, foundation) in state.foundations.enumerated() {
                if MoveValidator.canPlaceOnFoundation(topCard, topCard: foundation.last) {
                    hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .foundation(fIndex), priority: 1))
                }
            }
        }

        for (cellIndex, cell) in state.freeCells.enumerated() {
            guard let card = cell, MoveValidator.isAce(card) else { continue }
            for (fIndex, foundation) in state.foundations.enumerated() {
                if MoveValidator.canPlaceOnFoundation(card, topCard: foundation.last) {
                    hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .foundation(fIndex), priority: 1))
                }
            }
        }

        // Priority 2: Other foundation moves
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            guard let topCard = tableau.last, !MoveValidator.isAce(topCard) else { continue }
            for (fIndex, foundation) in state.foundations.enumerated() {
                if MoveValidator.canPlaceOnFoundation(topCard, topCard: foundation.last) {
                    hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .foundation(fIndex), priority: 2))
                }
            }
        }

        for (cellIndex, cell) in state.freeCells.enumerated() {
            guard let card = cell, !MoveValidator.isAce(card) else { continue }
            for (fIndex, foundation) in state.foundations.enumerated() {
                if MoveValidator.canPlaceOnFoundation(card, topCard: foundation.last) {
                    hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .foundation(fIndex), priority: 2))
                }
            }
        }

        // Priority 3: Tableau to tableau moves
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            guard let topCard = tableau.last else { continue }
            for (destIndex, _) in state.tableaus.enumerated() {
                guard destIndex != tabIndex else { continue }
                if canMove(cards: [topCard], from: .tableau(tabIndex), to: .tableau(destIndex), state: state) {
                    hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .tableau(destIndex), priority: 3))
                }
            }
        }

        // Priority 4: Free cell to tableau
        for (cellIndex, cell) in state.freeCells.enumerated() {
            guard let card = cell else { continue }
            for (tabIndex, _) in state.tableaus.enumerated() {
                if canMove(cards: [card], from: .freeCell(cellIndex), to: .tableau(tabIndex), state: state) {
                    hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .tableau(tabIndex), priority: 4))
                }
            }
        }

        // Priority 5: Move to free cell
        if state.freeCells.contains(where: { $0 == nil }) {
            for (tabIndex, tableau) in state.tableaus.enumerated() {
                guard let topCard = tableau.last else { continue }
                if let emptyCell = state.freeCells.firstIndex(where: { $0 == nil }) {
                    hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .freeCell(emptyCell), priority: 5))
                }
            }
        }

        return hints.sorted { $0.priority < $1.priority }
    }

    func canStackOnTableau(card: Card, onto target: Card) -> Bool {
        MoveValidator.isAlternatingColor(card, with: target)
            && MoveValidator.isDescending(card, onto: target)
    }

    func canPickUp(cards: [Card], from: CardLocation, state: GameSnapshot) -> Bool {
        guard !cards.isEmpty else { return false }

        switch from {
        case .tableau(let index):
            guard index >= 0, index < state.tableaus.count else { return false }
            // All cards must be face-up and form a valid alternating-color descending sequence
            guard cards.allSatisfy({ $0.isFaceUp }) else { return false }
            for i in 1..<cards.count {
                guard MoveValidator.isAlternatingColor(cards[i], with: cards[i - 1]),
                      MoveValidator.isDescending(cards[i], onto: cards[i - 1]) else {
                    return false
                }
            }
            return true

        case .freeCell(let index):
            guard cards.count == 1, index >= 0, index < state.freeCells.count else { return false }
            return state.freeCells[index] == cards.first

        case .foundation(let index):
            guard cards.count == 1, index >= 0, index < state.foundations.count else { return false }
            return cards[0] == state.foundations[index].last

        default:
            return false
        }
    }
}
