import Foundation

struct SpiderRules: GameRules {
    let variant: GameVariant = .spider

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

        // Deal 10 tableaus: first 4 get 6 cards, last 6 get 5 cards, top face-up
        for i in 0..<10 {
            let cardCount = i < 4 ? 6 : 5
            var column: [Card] = []
            for j in 0..<cardCount {
                var card = remaining.removeFirst()
                card.isFaceUp = (j == cardCount - 1)
                column.append(card)
            }
            tableaus.append(column)
        }

        return GameSnapshot(
            tableaus: tableaus,
            foundations: Array(repeating: [], count: 8),
            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 .tableau(let index):
            guard index >= 0, index < state.tableaus.count else { return false }
            let tableau = state.tableaus[index]
            if tableau.isEmpty { return true }
            guard let topCard = tableau.last, topCard.isFaceUp else { return false }
            return MoveValidator.isDescending(firstCard, onto: topCard)

        case .foundation:
            // Foundations are auto-filled when a complete K→A same-suit sequence is formed
            return false

        default:
            return false
        }
    }

    func drawFromStock(state: inout GameSnapshot, drawCount: Int) -> MoveAction? {
        // Spider deals one card to each tableau (must have at least one card per tableau)
        guard !state.stock.isEmpty else { return nil }
        guard state.tableaus.allSatisfy({ !$0.isEmpty }) else { return nil }

        var drawn: [Card] = []
        let dealCount = min(state.tableaus.count, state.stock.count)
        for i in 0..<dealCount {
            var card = state.stock.removeLast()
            card.isFaceUp = true
            state.tableaus[i].append(card)
            drawn.append(card)
        }

        return MoveAction(cards: drawn, from: .stock, to: .waste, didFlipCard: true, scoreChange: 0)
    }

    func scoreForMove(from: CardLocation, to: CardLocation) -> Int {
        switch (from, to) {
        case (.tableau, .foundation): return 100
        case (.tableau, .tableau): return 1
        default: return 0
        }
    }

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

    func canAutoComplete(state: GameSnapshot) -> Bool {
        guard state.stock.isEmpty else { return false }
        return state.tableaus.allSatisfy { column in
            column.allSatisfy { $0.isFaceUp }
        }
    }

    /// Check if the top cards of a tableau form a complete K→A same-suit sequence.
    func isCompleteSequence(in tableau: [Card]) -> Bool {
        guard tableau.count >= 13 else { return false }
        let sequence = tableau.suffix(13)
        let suit = sequence.first!.suit

        for (offset, card) in sequence.enumerated() {
            guard card.isFaceUp,
                  card.suit == suit,
                  card.rank.rawValue == 13 - offset else {
                return false
            }
        }
        return true
    }

    /// Check all tableaus for complete sequences and move them to foundations.
    func checkAndMoveCompleteSequences(state: inout GameSnapshot) -> Bool {
        var foundComplete = false
        for tabIndex in 0..<state.tableaus.count {
            if isCompleteSequence(in: state.tableaus[tabIndex]) {
                let sequence = Array(state.tableaus[tabIndex].suffix(13))
                state.tableaus[tabIndex].removeLast(13)
                // Find first empty foundation
                if let fIndex = state.foundations.firstIndex(where: { $0.isEmpty }) {
                    state.foundations[fIndex] = sequence
                    foundComplete = true
                }
                // Flip new top card
                if let lastIndex = state.tableaus[tabIndex].indices.last,
                   !state.tableaus[tabIndex][lastIndex].isFaceUp {
                    state.tableaus[tabIndex][lastIndex].isFaceUp = true
                }
            }
        }
        return foundComplete
    }

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

        for (tabIndex, tableau) in state.tableaus.enumerated() {
            let faceUpRun = sameSuitDescendingRun(in: tableau)
            guard !faceUpRun.isEmpty else { continue }

            for (destIndex, _) in state.tableaus.enumerated() {
                guard destIndex != tabIndex else { continue }
                if canMove(cards: faceUpRun, from: .tableau(tabIndex), to: .tableau(destIndex), state: state) {
                    let priority = (tableau.count - faceUpRun.count > 0 &&
                                   !tableau[tableau.count - faceUpRun.count - 1].isFaceUp) ? 2 : 4
                    hints.append(HintResult(cards: faceUpRun, from: .tableau(tabIndex), to: .tableau(destIndex), priority: priority))
                }
            }
        }

        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.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 }
            guard cards.allSatisfy({ $0.isFaceUp }) else { return false }
            // Must be a same-suit descending sequence
            for i in 1..<cards.count {
                guard MoveValidator.isSameSuit(cards[i], as: cards[i - 1]),
                      MoveValidator.isDescending(cards[i], onto: cards[i - 1]) else {
                    return false
                }
            }
            return true

        default:
            return false
        }
    }

    // MARK: - Helpers

    /// Returns the longest same-suit descending run from the bottom of the face-up portion.
    private func sameSuitDescendingRun(in tableau: [Card]) -> [Card] {
        let faceUp = Array(tableau.trailingSuffix(while: { $0.isFaceUp }))
        guard !faceUp.isEmpty else { return [] }

        var run = [faceUp.last!]
        for i in stride(from: faceUp.count - 2, through: 0, by: -1) {
            let card = faceUp[i]
            guard MoveValidator.isSameSuit(card, as: run[0]),
                  card.rank.rawValue == run[0].rank.rawValue + 1 else {
                break
            }
            run.insert(card, at: 0)
        }
        return run
    }
}
