import { Application, Assets, Container, EventEmitter, TilingSprite } from 'pixi.js'
import { Board } from '@/game/Board'
import { assets } from '@/game/utilities/assets'
import { Tile } from '@/game/Tile'
import { Hand } from '@/game/Hand'
import type {
  GameDto,
  MatchSessionDto,
  MatchSessionOptions,
  Movement,
  PlayerDto,
  TileDto,
} from '@/common/interfaces'
import type { SocketIoClientService } from '@/services/SocketIoClientService'
import { wait } from '@/common/helpers'
import { Actions } from 'pixi-actions'
import { OtherHand } from './OtherHand'
import { GameSummayView } from './GameSummayView'
import Config from './Config'
import { createText, grayStyle } from './utilities/fonts'
import { t } from '@/i18n'
import { DIRECTION_INDEXES, DIRECTIONS } from '@/common/constants'
export class Game extends EventEmitter {
  public board!: Board
  public hand!: Hand
  private app: Application = new Application()
  private selectedTile: TileDto | undefined
  private currentMove: Movement | undefined
  private otherHands: OtherHand[] = []
  private backgroundLayer: Container = new Container()
  private gameSummaryView!: GameSummayView
  private players: PlayerDto[] = []

  constructor(
    private options: MatchSessionOptions,
    private socketService: SocketIoClientService | undefined,
    private playerId: string,
    private sessionId: string,
  ) {
    super()
    this.options.screen = {
      width: 1280,
      height: 720,
      handScale: 1,
      boardScale: 0.7,
      ...this.options.screen,
    }
  }

  get stage() {
    return this.app.stage
  }

  async setup(): Promise<HTMLCanvasElement> {
    const width = this.options.screen?.width || 1280
    const height = this.options.screen?.height || 720

    await this.app.init({ width, height })
    this.app.ticker.add((tick) => Actions.tick(tick.deltaTime / 60))
    return this.app.canvas
  }

  async start(players: PlayerDto[] = []) {
    this.iniialStuff(this.app)
    this.board = new Board(this.app, this.options)
    this.hand = new Hand(this.app, this.options)
    this.otherHands = [
      new OtherHand(this.app, 'left'),
      new OtherHand(this.app, 'top'),
      new OtherHand(this.app, 'right'),
    ]
    this.initPlayers(players)
    this.players = players
    this.gameSummaryView = new GameSummayView(this.app, this.options)
    this.hand.scale = this.options.screen?.handScale || 1
    this.board.scale = this.options.screen?.boardScale || 0.7
    this.setBoardEvents()
    this.setHandEvents()
    this.initEventBus()
    await wait(500)
    this.socketService &&
      this.socketService.sendMessage('client:set-client-ready', {
        sessionId: this.sessionId,
        userId: this.playerId,
      })
  }

  iniialStuff(app: Application) {
    app.stage.addChild(this.backgroundLayer)
    const background = new TilingSprite({
      texture: Assets.get(`bg-${this.options.background}`),
      width: app.canvas.width,
      height: app.canvas.height,
    })

    this.backgroundLayer.addChild(background)

    const actor = this.options.teamed ? t('team') : t('player')
    const type =
      this.options.winType === 'points'
        ? t('n-points', this.options.winTarget)
        : t('n-rounds', this.options.winTarget)
    const helptext = t('first-actor-to-win-this-options-wintarget-this-options-wintype', [
      actor.toLowerCase(),
      type.toLowerCase(),
    ])

    this.backgroundLayer.addChild(
      createText({
        text: `${helptext}`,
        x: this.app.canvas.width / 2,
        y: 120,
        style: grayStyle(14, 'lighter', false),
      }),
    )
  }

  initPlayers(players: PlayerDto[]) {
    const myIndex = players.findIndex((player) => player.id === this.playerId)
    const copy = [...players]
    const cut = copy.splice(myIndex)
    const player = cut.shift()
    const final = cut.concat(copy)

    for (let i = 0; i < final.length; i++) {
      const hand = this.otherHands[i]
      hand.setPlayer(final[i])
    }
    this.hand.setPlayer(player)

    this.board.otherPlayerHands = this.otherHands
  }

  updateOtherHands(players: PlayerDto[]) {
    players.forEach((player) => {
      const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
      if (hand) {
        hand.setHand(player.hand)
      }
    })
  }

  highLightPlayer(player: PlayerDto) {
    const hand = this.otherHands.find((hand) => hand.player?.id === player.id)
    if (hand) {
      hand.setActive(true)
    }
  }

  setPlayersInactive() {
    this.otherHands.forEach((hand) => hand.setActive(false))
    this.hand.setActive(false)
  }

  async preload() {
    await Assets.load(assets)
  }

  destroy() {
    this.removeBoardEvents()
    this.removeHandEvents()
    this.app.destroy()
  }

  private initEventBus() {}

  private setHandEvents() {
    this.hand.on('hand-updated', (tiles: Tile[]) => {
      this.board.setPlayerHand(tiles)
    })
    this.hand.on('game:tile-click', (tile: TileDto) => this.highlightMoves(tile))
    this.hand.on('game:timer-timeout', this.handleHandPlayerTimeout.bind(this))

    this.hand.on('game:button-pass-click', this.sendPassEvent.bind(this))

    this.gameSummaryView.on('finishClick', (data) => {
      this.emit('game:finish-click', data)
    })

    this.gameSummaryView.on('nextClick', (data) => {
      this.board.clean()
      this.updateScoreboard(data.sessionState)
      this.socketService &&
        this.socketService.sendMessage('client:set-client-ready-for-next-game', {
          userId: this.playerId,
          sessionId: this.sessionId,
        })
    })

    this.hand.on('hand-initialized', () => {})
  }

  private handleHandPlayerTimeout(tile: TileDto | null) {
    if (tile === null) {
      this.sendPassEvent()
      this.hand.disablePassButton()
      return
    }
    const freeEnds = this.board.freeEnds
    console.log('freeEnds :>> ', freeEnds, this.playerId)
    const move: Movement = {
      id: '',
      tile,
      type: 'right',
      playerId: this.playerId,
    }
    if (freeEnds !== undefined) {
      const side = tile.pips?.includes(freeEnds[0]) ? 'left' : 'right'
      const direction = side === 'left' ? this.board.leftDirection : this.board.rightDirection
      let dirIndex = DIRECTION_INDEXES[direction]
      const validMoves = this.board.nextTileValidMoves(tile, side)
      const validPoints = this.board.nextTileValidPoints(tile, side, validMoves)
      let safeCount = 4
      while (safeCount > 0 && !validMoves[dirIndex % 4]) {
        dirIndex += 1
        safeCount -= 1
      }
      console.log('validMoves :>> ', validMoves)
      console.log('validPoints :>> ', validPoints)
      const validPoint = validPoints[dirIndex % 4]
      if (validPoint !== undefined) {
        move.x = validPoint[0]
        move.y = validPoint[1]
      }
      move.direction = DIRECTIONS[dirIndex % 4]
      move.type = side
    }
    this.currentMove = move
    this.board.updateBoard(move, this.hand.tileMoved(tile))
  }

  private async sendPassEvent() {
    const move: Movement = {
      id: '',
      type: 'pass',
      playerId: this.playerId,
    }
    this.socketService &&
      this.socketService.sendMessage('client:player-move', {
        sessionId: this.sessionId,
        move: move,
      })
    await this.board.updateBoard(move, undefined)
  }

  private updateScoreboard(sessionState: MatchSessionDto) {
    const scoreboard = sessionState.scoreboard
    const myScore = scoreboard.find((d) => d.id === this.playerId)?.score || 0
    this.hand.setScore(myScore)
    this.otherHands.forEach((otherHand) => {
      const player: PlayerDto | undefined = otherHand.player
      const name: string = player?.name || ''
      const score = scoreboard.find((d) => d.name === name)?.score || 0
      otherHand.setScore(score)
    })
  }

  highlightMoves(tile: TileDto) {
    this.selectedTile = tile
    if (tile !== undefined) {
      this.board.setMovesForTile(this.getMoves(tile), tile)
    } else {
      this.board.cleanInteractions()
    }
  }

  getMoves(tile: any): [boolean, boolean] {
    if (tile === undefined) return [false, false]
    if (this.board.count === 0) return [false, true]

    const validEnds: [boolean, boolean] = [false, false]
    const freeEnds = this.board.freeEnds

    if (freeEnds !== undefined) {
      if (tile.pips != undefined) {
        validEnds[0] = tile.pips.includes(freeEnds[0])
        validEnds[1] = tile.pips.includes(freeEnds[1])
      }
    }
    return validEnds
  }

  async setNextPlayer(state: GameDto) {
    const currentPlayer = state?.currentPlayer!
    this.setPlayersInactive()
    if (currentPlayer.id === this.playerId) {
      this.hand.setActive(true)
      this.hand.prepareForMove(this.board.count === 0, this.board.freeEnds)
      this.board.setPlayerTurn(currentPlayer)
    } else {
      this.board.setServerPlayerTurn(currentPlayer)
      this.highLightPlayer(currentPlayer)
    }
  }

  private setBoardEvents() {
    this.board.on('game:board-left-action-click', async (data) => {
      if (this.selectedTile === undefined) return
      const move: Movement = {
        tile: this.selectedTile,
        type: 'left',
        playerId: this.playerId,
        ...data,
      }
      this.currentMove = move
      const tile = this.hand.tileMoved(this.selectedTile)
      await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
    })

    this.board.on('game:board-right-action-click', async (data) => {
      if (this.selectedTile === undefined) return
      const move: Movement = {
        tile: this.selectedTile,
        type: 'right',
        playerId: this.playerId,
        ...data,
      }
      this.currentMove = move
      const tile = this.hand.tileMoved(this.selectedTile)
      await this.board.updateBoard({ ...move, tile: this.selectedTile }, tile)
    })

    this.board.on('game:tile-animation-ended', async (tile) => {
      if (tile !== null && tile !== undefined && tile.playerId === this.playerId) {
        this.socketService &&
          this.socketService.sendMessage('client:player-move', {
            sessionId: this.sessionId,
            move: this.currentMove,
          })
      } else {
        this.socketService &&
          this.socketService.sendMessage('client:animation-ended', {
            sessionId: this.sessionId,
            userId: this.playerId,
          })
      }
    })
  }

  async gameFinished(data: any) {
    await wait(Config.waitMillisToShowSummary)
    this.updateOtherHands(data.lastGame.players)
    this.hand.gameFinished()
    this.board.gameFinished()
    this.setPlayersInactive()
    this.gameSummaryView.setGameSummary(data, 'round')
  }

  async matchFinished(data: any) {
    await wait(Config.waitMillisToShowSummary)
    this.updateOtherHands(data.lastGame.players)
    this.board.matchFinished()
    this.gameSummaryView.setGameSummary(data, 'match')
  }

  serverPlayerMove(data: any, playerId: string) {
    this.board.playerMove(data, playerId)

    if (!(data.move === undefined || data.move === null)) {
      const otherHand = this.otherHands.find((hand) => hand.player?.id === data.move.playerId)
      if (otherHand) {
        otherHand.update(data.move)
      }
    } else {
      // ShowPassed
    }
  }

  private removeBoardEvents() {
    this.board.off('game:board-left-action-click')
    this.board.off('game:board-right-action-click')
  }

  private removeHandEvents() {
    this.hand.off('game:tile-click')
    this.hand.off('game:button-pass-click')
  }
}
