import Foundation

struct KlondikeRules: GameRules {
    let variant: GameVariant = .klondike

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

        // Deal 7 tableaus: column i gets i+1 cards, top card face-up
        for i in 0..<7 {
            var column: [Card] = []
            for j in 0...i {
                var card = remaining.removeFirst()
                card.isFaceUp = (j == i)
                column.append(card)
            }
            tableaus.append(column)
        }

        return GameSnapshot(
            tableaus: tableaus,
            foundations: [[], [], [], []],
            stock: remaining,
            waste: [],
            freeCells: [],
            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 }
            let foundation = state.foundations[index]
            return MoveValidator.canPlaceOnFoundation(firstCard, topCard: foundation.last)

        case .tableau(let index):
            guard index >= 0, index < state.tableaus.count else { return false }
            let tableau = state.tableaus[index]
            if tableau.isEmpty {
                return MoveValidator.isKing(firstCard)
            }
            guard let topCard = tableau.last, topCard.isFaceUp else { return false }
            return MoveValidator.isAlternatingColor(firstCard, with: topCard)
                && MoveValidator.isDescending(firstCard, onto: topCard)

        case .waste, .stock, .freeCell:
            return false
        }
    }

    func drawFromStock(state: inout GameSnapshot, drawCount: Int) -> MoveAction? {
        if state.stock.isEmpty {
            guard !state.waste.isEmpty else { return nil }
            // Reset: move waste back to stock, reversed
            state.stock = state.waste.reversed().map { card in
                var c = card
                c.isFaceUp = false
                return c
            }
            state.waste = []
            return MoveAction(cards: [], from: .waste, to: .stock, didFlipCard: false, scoreChange: 0)
        }

        var drawn: [Card] = []
        for _ in 0..<min(drawCount, state.stock.count) {
            var card = state.stock.removeLast()
            card.isFaceUp = true
            drawn.append(card)
        }
        state.waste.append(contentsOf: drawn)
        return MoveAction(cards: drawn, from: .stock, to: .waste, didFlipCard: true, scoreChange: 0)
    }

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

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

    func canAutoComplete(state: GameSnapshot) -> Bool {
        guard state.stock.isEmpty, state.waste.isEmpty else { return false }
        // All tableau cards must be face-up
        return state.tableaus.allSatisfy { column in
            column.allSatisfy { $0.isFaceUp }
        }
    }

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

        // Priority 1: Aces to foundation
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            guard let topCard = tableau.last, topCard.isFaceUp, 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))
                }
            }
        }

        // Priority 1: Ace from waste
        if let wasteTop = state.waste.last, MoveValidator.isAce(wasteTop) {
            for (fIndex, foundation) in state.foundations.enumerated() {
                if MoveValidator.canPlaceOnFoundation(wasteTop, topCard: foundation.last) {
                    hints.append(HintResult(cards: [wasteTop], from: .waste, to: .foundation(fIndex), priority: 1))
                }
            }
        }

        // Priority 2: Other foundation moves
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            guard let topCard = tableau.last, topCard.isFaceUp, !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))
                }
            }
        }

        // Priority 3: Tableau moves that reveal face-down cards
        for (tabIndex, tableau) in state.tableaus.enumerated() {
            let faceUpCards = tableau.trailingSuffix(while: { $0.isFaceUp })
            guard !faceUpCards.isEmpty else { continue }
            let hasFaceDown = tableau.count > faceUpCards.count

            guard hasFaceDown else { continue }

            for (destIndex, _) in state.tableaus.enumerated() {
                guard destIndex != tabIndex else { continue }
                let cardsToMove = Array(faceUpCards)
                if canMove(cards: cardsToMove, from: .tableau(tabIndex), to: .tableau(destIndex), state: state) {
                    hints.append(HintResult(cards: cardsToMove, from: .tableau(tabIndex), to: .tableau(destIndex), priority: 3))
                }
            }
        }

        // Priority 4: Waste to tableau
        if let wasteTop = state.waste.last {
            for (tabIndex, _) in state.tableaus.enumerated() {
                if canMove(cards: [wasteTop], from: .waste, to: .tableau(tabIndex), state: state) {
                    hints.append(HintResult(cards: [wasteTop], from: .waste, to: .tableau(tabIndex), priority: 4))
                }
            }
        }

        // Priority 5: Draw from stock
        if !state.stock.isEmpty {
            hints.append(HintResult(cards: [], from: .stock, to: .waste, 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 .waste:
            return cards.count == 1 && cards[0] == state.waste.last

        case .tableau(let index):
            guard index >= 0, index < state.tableaus.count else { return false }
            let tableau = state.tableaus[index]
            guard cards.allSatisfy({ $0.isFaceUp }) else { return false }
            // Cards must be a valid sequence from the bottom of the face-up portion
            let faceUpCards = tableau.trailingSuffix(while: { $0.isFaceUp })
            guard cards.count <= faceUpCards.count else { return false }
            let startIdx = faceUpCards.count - cards.count
            let expectedCards = Array(faceUpCards.dropFirst(startIdx))
            return cards == expectedCards

        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
        }
    }
}
