import { PlayingCard } from 'tarot-card-deck';
import { Observable } from 'rxjs';

declare type CardGamePlayerNotification = {
    type: "GOT_AVAILABLE_CARDS";
    cards: PlayingCard[];
} | {
    type: "ASKED_TO_PLAY";
    playableCards: readonly PlayingCard[];
} | {
    type: "ERROR_WHILE_PLAYING";
} | {
    type: "PLAYER_HAS_PLAYED";
    player: PlayerIdentifier;
    card: PlayingCard;
} | {
    type: "TURN_RESULT_IS_KNOWN";
    turnWinner: PlayerIdentifier;
};

declare type PlayerIdentifier = string;
interface CardGamePlayer {
    id: PlayerIdentifier;
    notify(playerNotification: CardGamePlayerNotification): any;
}

declare enum Announce {
    PRISE = "PRISE",
    GARDE = "GARDE",
    GARDE_SANS = "GARDE_SANS",
    GARDE_CONTRE = "GARDE_CONTRE"
}

declare type TarotPlayerNotification = CardGamePlayerNotification | {
    type: "ASKED_FOR_ANNOUNCE";
    availableAnnounces: Announce[];
} | {
    type: "ERROR_WHILE_ANNOUNCING";
} | {
    type: "PLAYER_HAS_ANNOUNCED";
    player: PlayerIdentifier;
    announce?: Announce;
} | {
    type: "TAKER_IS_KNOWN";
    player: PlayerIdentifier;
    announce?: Announce;
} | {
    type: "GAME_IS_ABORTED";
} | {
    type: "GAME_IS_OVER";
    numberOfPointsForAttack: number;
    numberOfPointsForDefense: number;
    finalScores: readonly PlayerWithScore[];
} | {
    type: "ASKED_FOR_SET_ASIDE";
    possibleCardsToSetAside: PlayingCard[];
} | {
    type: "ERROR_WHILE_SETTING_ASIDE";
} | {
    type: "ASKED_FOR_POIGNEE_ANNOUNCE";
    numberOfCardsToShow: number;
    possibleCardsToShow: PlayingCard[];
} | {
    type: "ERROR_WHILE_ANNOUNCING_POIGNEE";
} | {
    type: "POIGNEE_HAS_BEEN_ANNOUNCED";
    player: PlayerIdentifier;
    shownCards: PlayingCard[];
};

interface TarotPlayer extends CardGamePlayer {
    id: PlayerIdentifier;
    notify(playerNotification: TarotPlayerNotification): void;
}

declare type TakerAnnounce = {
    taker: TarotPlayer;
    announce: Announce;
};

interface AnnounceManager {
    beginAnnounces(): void;
    announce(playerThatAnnounce: TarotPlayer, announce?: Announce): void;
    announcesAreComplete(): Observable<TakerAnnounce>;
}

declare type PlayedCard = {
    playingCard: PlayingCard;
    playerIdentifier: PlayerIdentifier;
};

interface PlayerTurnPlugin {
    apply(turnNumber: any, player: CardGamePlayer, playerCards: PlayingCard[]): Observable<unknown>;
}

declare type Trick = {
    winner: PlayerIdentifier;
    cards: readonly PlayedCard[];
};
interface CardGameManager {
    begin(): void;
    gameIsOver(): Observable<Trick[]>;
    play(player: CardGamePlayer, card: PlayingCard): any;
    registerPlayerTurnPlugin(plugin: PlayerTurnPlugin): void;
}

interface TarotTable {
    shuffle(): void;
    cut(): void;
    gatherDeck(): readonly PlayingCard[];
    giveCardTo(cardIdentifier: string, player: PlayerIdentifier): void;
    putCardInDog(cardIdentifier: string): void;
    listCardsOf(player: PlayerIdentifier): PlayingCard[];
    listPointsFor(player: PlayerIdentifier): PlayingCard[];
    giveDogToPlayerHand(player: PlayerIdentifier): void;
    giveDogToPlayerPoints(player: PlayerIdentifier): void;
    moveFromHandToPointsOf(wonCards: PlayingCard[], playerThatGetCards: PlayerIdentifier): void;
}

interface TarotDealer {
    deal(asideNumberOfCards: number): void;
}

declare type GetIncorrectCardsSetAside = (allAvailableCards: PlayingCard[], cardsSetAside: PlayingCard[]) => PlayingCard[];
declare type GetPossibleCardsToSetAside = (allAvailableCards: PlayingCard[], numberOfCardsToSetAside: number) => PlayingCard[];

declare type CountEndGameTakerPoints = (takerWonCards: PlayingCard[], takerHasExcuseAtStartOfGame: boolean) => number;

declare enum Poignee {
    SIMPLE = "SIMPLE",
    DOUBLE = "DOUBLE",
    TRIPLE = "TRIPLE"
}

declare type Team = "ATTACK" | "DEFENSE";
declare type EndGameStatus = {
    announce: Announce;
    attackNumberOfPoints: number;
    poignee?: Poignee;
    petitInLastTrick?: Team;
    attackNumberOfOudlers: number;
};
declare type EndGameScore = {
    attackScoreByPlayer: number;
    defenseScoreByPlayer: number;
};
declare type CountEndGameScore = (endGameStatus: EndGameStatus) => EndGameScore;

declare class PoigneeCardGamePlugin implements PlayerTurnPlugin {
    private readonly players;
    private readonly poigneesManagerByPlayers;
    private potentialPoignee;
    constructor(players: readonly CardGamePlayer[]);
    apply(turnNumber: number, player: CardGamePlayer, playerCards: PlayingCard[]): Observable<unknown>;
    decline(player: CardGamePlayer): void;
    announce(player: CardGamePlayer, shownCards: PlayingCard[]): void;
    getPotentialPoignee(): Poignee;
    private static notifyErrorWhileAnnouncing;
}

declare type PlayerWithScore = {
    player: PlayerIdentifier;
    score: number;
};
declare type GameResultWithDeck = {
    numberOfPointsForAttack: number;
    numberOfPointsForDefense: number;
    finalScores: readonly PlayerWithScore[];
    endOfGameDeck: readonly PlayingCard[];
};
declare class TarotGame {
    private readonly players;
    private readonly table;
    private readonly dealer;
    private readonly announceManager;
    private readonly cardGameManager;
    private readonly poigneePlugin;
    private readonly verifyCardsSetAside;
    private readonly getPossibleCardsToSetAside;
    private readonly countEndGameTakerPoints;
    private readonly countEndGameScore;
    private readonly endOfGameCallback;
    private readonly numberOfCardsInDog;
    private taker;
    private takerAnnounce;
    private gameHasBegan;
    private takerHasExcuseAtStartOfGame;
    constructor(players: readonly TarotPlayer[], table: TarotTable, dealer: TarotDealer, announceManager: AnnounceManager, cardGameManager: CardGameManager, poigneePlugin: PoigneeCardGamePlugin, verifyCardsSetAside: GetIncorrectCardsSetAside, getPossibleCardsToSetAside: GetPossibleCardsToSetAside, countEndGameTakerPoints: CountEndGameTakerPoints, countEndGameScore: CountEndGameScore, endOfGameCallback: (gameResult: GameResultWithDeck) => void);
    announce(playerThatAnnounce: TarotPlayer, announce?: Announce): void;
    setAside(playerThatSetAside: TarotPlayer, cardsSetAside: PlayingCard[]): void;
    declinePoignee(player: TarotPlayer): void;
    announcePoignee(player: TarotPlayer, shownCards: PlayingCard[]): void;
    play(playerThatPlay: TarotPlayer, card: PlayingCard): void;
    private beginGame;
    private resolveTakerAndContinueOrEndGame;
    private managePriseOrGarde;
    private manageGardeSans;
    private manageGardeContre;
    private noTakerGameResult;
    private endGame;
    private endedGameResult;
    private resolveTeamThatPlayedPetitInLastTrick;
    private static notifyTakerIsKnown;
    private static notifyPlayerHasToSetAside;
    private static notifyErrorWhileSettingAside;
    private static notifyGameIsOver;
    private static notifyGameAborted;
    private static notifyCardsAvailable;
}

declare type DealtCards = {
    playersDecks: PlayingCard[][];
    dog: PlayingCard[];
};
declare type DealFunction = (deck: readonly PlayingCard[], numberOfPlayers: number, numberOfCardsInDog: any) => DealtCards;

declare function getTarotGame(playingCards: readonly PlayingCard[], players: readonly TarotPlayer[], endOfGameCallback: (gameResult: GameResultWithDeck) => void): TarotGame;
declare function getTarotGameWithCustomDealFunction(playingCards: readonly PlayingCard[], players: readonly TarotPlayer[], endOfGameCallback: (gameResult: GameResultWithDeck) => void, dealFunction: DealFunction): TarotGame;

export { Announce, GameResultWithDeck, PlayerIdentifier, PlayerWithScore, TarotGame, TarotPlayer, TarotPlayerNotification, getTarotGame, getTarotGameWithCustomDealFunction };
