import { Application, Container, EventEmitter, Text, Ticker } from 'pixi.js'
import { Scale, type ScaleFunction } from '@/game/utilities/scale'
import type {
  AnimationOptions,
  MatchSessionOptions,
  Movement,
  PlayerDto,
  TileDto,
} from '@/common/interfaces'
import { Tile } from '@/game/Tile'
import { createContainer, isTilePair } from '@/common/helpers'
import { createText } from '@/game/utilities/fonts'
import { LoggingService } from '@/services/LoggingService'
import { GlowFilter } from 'pixi-filters'
import { DIRECTION_INDEXES, DIRECTIONS, ORIENTATION_ANGLES } from '@/common/constants'
import type { OtherHand } from './OtherHand'
import { SoundManager } from '@/game/utilities/SoundManager'
import { t } from '@/i18n'

export class Board extends EventEmitter {
  private _scale: number = 1
  private _canMove: boolean = false
  private logger: LoggingService = new LoggingService()
  private soundManager: SoundManager = new SoundManager()

  ticker: Ticker
  height: number
  width: number
  grain: number = 25
  scaleY: ScaleFunction
  scaleX: ScaleFunction
  container!: Container
  initialContainer!: Container
  tilesContainer!: Container
  leftContainer!: Container
  rightContainer!: Container
  interactionContainer!: Container
  textContainer!: Container
  tiles: Tile[] = []
  boneyard: Tile[] = []
  movements: any[] = []
  freeEnds?: [number, number]
  leftTile?: Tile
  rightTile?: Tile
  nextTile?: Tile
  textWaitForPlayers!: Text
  textYourTurn!: Text
  leftDirection: string = 'west'
  rightDirection: string = 'east'
  playerHand: Tile[] = []
  firstTile?: Tile
  currentPlayer!: PlayerDto
  otherPlayerHands: OtherHand[] = []

  constructor(
    app: Application,
    private options: MatchSessionOptions,
  ) {
    super()
    this.ticker = app.ticker
    this.height = app.canvas.height - 130 - 130
    this.width = app.canvas.width
    this.scaleX = Scale([0, this.width], [0, this.width])
    this.scaleY = Scale([0, this.height], [0, this.height])
    this.calculateScale()

    this.container = createContainer({
      y: 130,
      width: this.width,
      height: this.height,
      parent: app.stage,
    })

    // this.container.y = 130

    this.initialContainer = createContainer({
      width: this.width,
      height: this.height,
      // color: 0x1e2f23,
      visible: false,
      parent: this.container,
    })

    this.tilesContainer = createContainer({
      width: this.width,
      height: this.height,
      // color: 0x1e2f23,
      parent: this.container,
    })

    this.interactionContainer = createContainer({
      width: this.width,
      height: this.height,
      parent: this.container,
      visible: false,
    })

    // createCrosshair(this.tilesContainer, 0xff0000, {
    //   width: this.width,
    //   height: this.height,
    //   x: this.scaleX(0),
    //   y: this.scaleY(0),
    // })

    this.textContainer = createContainer({
      width: this.width,
      height: this.height,
      parent: this.container,
    })

    this.showText(t('game.starting_game'))
  }

  private calculateScale() {
    const scaleXSteps = Math.floor(this.width / (this.grain * this.scale)) / 2
    const scaleYSteps = Math.floor(this.height / (this.grain * this.scale)) / 2
    this.scaleX = Scale([-scaleXSteps, scaleXSteps], [0, this.width])
    this.scaleY = Scale([-scaleYSteps, scaleYSteps], [0, this.height])
  }

  get count() {
    return this.tiles.length
  }

  get scale() {
    return this._scale
  }

  set scale(value: number) {
    this._scale = value
    this.calculateScale()
  }

  setPlayerHand(tiles: Tile[]) {
    this.playerHand = tiles
  }

  showText(text: string) {
    this.textContainer.removeChildren()
    this.textContainer.addChild(
      createText({
        text,
        x: this.scaleX(0),
        y: this.height - 24,
      }),
    )
  }

  async setPlayerTurn(player: PlayerDto) {
    this.showText(t('game.your-turn'))
  }

  async setServerPlayerTurn(currentPlayer: PlayerDto) {
    this.showText(t('game.player-turn', [currentPlayer.name]))
  }

  async playerMove(move: any, playerId: string) {
    const { move: lastMove } = move
    if (lastMove === null) {
      // Player passed
      setTimeout(() => {
        this.emit('game:tile-animation-ended')
      }, 500)
      return
    }
    if (
      lastMove !== null &&
      lastMove.tile !== undefined &&
      lastMove.tile.pips !== undefined &&
      lastMove.playerId !== playerId
    ) {
      const tile = new Tile(lastMove.tile.id, this.ticker, lastMove.tile.pips, this.scale)
      this.nextTile = tile
      lastMove.tile = tile.toPlain()
      this.movements.push(lastMove)
      await this.addTile(tile, lastMove)
      this.setFreeEnd(lastMove)
    }
  }

  async addTile(tile: Tile, move: Movement) {
    let orientation = ''
    let x: number =
      move.type === 'left'
        ? this.scaleX.inverse(this.leftTile?.x ?? 0)
        : this.scaleX.inverse(this.rightTile?.x ?? 0)
    let y: number =
      move.type === 'left'
        ? this.scaleY.inverse(this.leftTile?.y ?? 0)
        : this.scaleY.inverse(this.rightTile?.y ?? 0)
    const isLeft = move.type === 'left'
    const tileDto = tile.toPlain()
    let direction = move.type === 'left' ? this.leftDirection : this.rightDirection

    move.direction = this.hasSpaceToMove(move)

    if (this.tiles.length === 0) {
      x = 0
      y = 0
      if (tile.isPair()) {
        orientation = 'north'
      } else {
        orientation = 'east'
      }
      this.firstTile = tile
    } else if (move.direction !== undefined) {
      direction = move.direction
      const availableMoves = this.nextTileValidMoves(tileDto, move.type)
      const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)
      const directionIndex = DIRECTIONS.indexOf(direction)
      isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
      const availablePosition: [number, number] | undefined = availablePositions[directionIndex]
      orientation = this.getOrientation(tileDto, move.type)
      availablePosition && ([x, y] = availablePosition)
    } else {
      if (this.tiles.length === 0) {
        x = 0
        y = 0
        if (tile.isPair()) {
          tile.setOrientation('north')
        } else {
          tile.setOrientation('east')
        }
      } else {
        const availableMoves = this.nextTileValidMoves(tileDto, move.type)
        const availablePositions = this.nextTileValidPoints(tileDto, move.type, availableMoves)

        let directionIndex = DIRECTIONS.indexOf(direction)
        let availablePosition: [number, number] | undefined = availablePositions[directionIndex]
        let endlessLoop: number = 0
        while (endlessLoop < 4 && availablePosition === undefined) {
          directionIndex = (directionIndex + 1) % 4
          availablePosition = availablePositions[directionIndex]
          endlessLoop++
        }
        if (endlessLoop >= 4) {
          throw new Error('No available position')
        }
        direction = DIRECTIONS[directionIndex]
        isLeft ? (this.leftDirection = direction) : (this.rightDirection = direction)
        orientation = this.getOrientation(tileDto, move.type)
        availablePosition && ([x, y] = availablePosition)
      }
    }
    tile.addTo(this.tilesContainer)
    tile.reScale(this.scale)
    this.tiles.push(tile)
    const moveSound = this.getRandomClickSound()
    await this.animateTile(tile, x, y, orientation, move)
    this.soundManager.play(moveSound)
    this.emit('game:tile-animation-ended', tile.toPlain())
  }

  getRandomClickSound() {
    const sounds = ['snd-move-1', 'snd-move-2', 'snd-move-3', 'snd-move-4']
    const index = Math.floor(Math.random() * sounds.length)
    return sounds[index]
  }

  async animateTile(tile: Tile, x: number, y: number, orientation: string, move: Movement) {
    const targetX = this.scaleX(x)
    const targetY = this.scaleY(y)
    const animation: AnimationOptions = {
      x: targetX,
      y: targetY,
      rotation: ORIENTATION_ANGLES[orientation],
      duration: 20,
    }
    const tempAlpha = tile.alpha
    tile.alpha = 0
    const clonedTile = tile.clone()
    clonedTile.addTo(this.tilesContainer)
    const pos = this.getAnimationInitialPosition(move)
    clonedTile.setPosition(this.scaleX(pos.x), this.scaleY(pos.y))
    await clonedTile.animateTo(animation)
    clonedTile.removeFromParent()
    tile.setOrientation(orientation)
    tile.setPosition(targetX, targetY)
    tile.alpha = tempAlpha
  }

  getAnimationInitialPosition(move: Movement): { x: number; y: number } {
    const otherHand = this.otherPlayerHands.find((h) => h.player?.id === move.playerId)
    if (otherHand === undefined) {
      return { x: 0, y: this.scaleY.inverse(this.height + 50) }
    }
    const position = otherHand.position
    switch (position) {
      case 'left':
        return { x: this.scaleX.inverse(100), y: this.scaleY.inverse(100) }
      case 'right':
        return { x: this.scaleX.inverse(this.width - 100), y: this.scaleY.inverse(20) }
      case 'top':
        return { x: 0, y: this.scaleY.inverse(100) }
    }
  }

  getPlayedTile(id: string): Tile | undefined {
    return this.tiles.find((t) => t.id === id)
  }

  getTileInHand(id: string): Tile | undefined {
    return this.playerHand.find((t) => t.id === id)
  }

  setFreeEnd(move: Movement) {
    const { type, tile: tileDto } = move
    const tile = this.getPlayedTile(tileDto?.id ?? '')

    if (tile === undefined) {
      return
    }

    const { pips } = tile

    if (pips === undefined) {
      return
    }

    if (this.freeEnds === undefined) {
      this.leftTile = this.rightTile = tile
      if (tile.isPair()) {
        this.freeEnds = [pips[0], pips[1]]
      } else {
        this.freeEnds = [pips[1], pips[0]]
      }
    } else if (type === 'left') {
      this.leftTile = tile
      if (tile.isPair()) {
        this.freeEnds[0] = pips[0]
      } else {
        this.freeEnds[0] = pips[0] === this.freeEnds[0] ? pips[1] : pips[0]
      }
    } else if (type === 'right') {
      this.rightTile = tile
      if (tile.isPair()) {
        this.freeEnds[1] = pips[0]
      } else {
        this.freeEnds[1] = pips[0] === this.freeEnds[1] ? pips[1] : pips[0]
      }
    }
  }

  async updateBoard(move: Movement, tile: Tile | undefined) {
    try {
      // const { tileDto: tileDto } = move
      // const tile = this.getTileInHand(tileDto?.id ?? '')
      this.movements.push(move)
      if (tile === undefined) {
        return
      }
      await this.addTile(tile, move)
      this.setFreeEnd(move)
    } catch (error) {
      this.logger.error(error, 'Error updating board')
    }
  }

  setMovesForTile(values: [boolean, boolean], tile: TileDto) {
    this.setValidEnds(values, tile)
  }

  setValidEnds(values: boolean[], tile: TileDto) {
    if (this.count === 0) {
      this.createInteractions('right', [[0, 0], undefined, undefined, undefined])
      return
    }

    if (values[0]) {
      const side = 'left'
      const validInteractions = this.nextTileValidMoves(tile, side)
      const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
      this.createInteractions(side, validPoints)
    }
    if (values[1]) {
      const side = 'right'
      const validInteractions = this.nextTileValidMoves(tile, side)
      const validPoints = this.nextTileValidPoints(tile, side, validInteractions)
      this.createInteractions(side, validPoints)
    }
  }

  nextTileValidPoints(
    tile: TileDto,
    side: string,
    validMoves: boolean[],
  ): ([number, number] | undefined)[] {
    const isLeft = side === 'left'
    const end = isLeft ? this.leftTile : this.rightTile
    const isEndVertical = end?.isVertical() ?? false
    const direction = isLeft ? this.leftDirection : this.rightDirection
    const signX = direction === 'west' ? -1 : 1
    const signY = direction === 'south' ? 1 : -1
    const isEndPair = end?.isPair() ?? false

    const x = this.scaleX.inverse(end?.x ?? 0)
    const y = this.scaleY.inverse(end?.y ?? 0)

    let pointNorth: [number, number] | undefined = undefined
    let pointSouth: [number, number] | undefined = undefined
    let pointEast: [number, number] | undefined = undefined
    let pointWest: [number, number] | undefined = undefined

    const incX = isTilePair(tile) ? 3 : 4

    if (this.count !== 0) {
      if (validMoves[0]) {
        // north
        if (isEndVertical) {
          pointNorth = [x, y - incX]
        } else {
          pointNorth = isEndPair ? [x, y - 3] : [x + 1 * signX, y - 3]
        }
      }
      if (validMoves[2]) {
        // south
        if (isEndVertical) {
          pointSouth = [x, y + incX]
        } else {
          pointSouth = isEndPair ? [x, y + 3] : [x + 1 * signX, y + 3]
        }
      }
      if (validMoves[1]) {
        // east
        if (isEndVertical) {
          pointEast = isEndPair ? [x + 3, y] : [x + 3, y + 1 * signY]
        } else {
          pointEast = [x + incX, y]
        }
      }
      if (validMoves[3]) {
        // west
        if (isEndVertical) {
          pointWest = isEndPair ? [x - 3, y] : [x - 3, y + 1 * signY]
        } else {
          pointWest = [x - incX, y]
        }
      }
    }

    return [pointNorth, pointEast, pointSouth, pointWest]
  }

  createInteractions(side: string, validInteractions: ([number, number] | undefined)[]) {
    if (validInteractions[0] !== undefined) {
      this.addInteraction(validInteractions[0][0], validInteractions[0][1], side, 'north')
    }
    if (validInteractions[1] !== undefined) {
      this.addInteraction(validInteractions[1][0], validInteractions[1][1], side, 'east')
    }
    if (validInteractions[2] !== undefined) {
      this.addInteraction(validInteractions[2][0], validInteractions[2][1], side, 'south')
    }
    if (validInteractions[3] !== undefined) {
      this.addInteraction(validInteractions[3][0], validInteractions[3][1], side, 'west')
    }
  }

  cleanInteractions() {
    this.interactionContainer.removeChildren()
  }

  addInteraction(x: number, y: number, side: string, direction?: string) {
    const dot = new Tile('ghost', this.ticker, undefined, this.scale)
    dot.setFilters([
      new GlowFilter({
        distance: 5,
        outerStrength: 1,
        innerStrength: 0,
        color: 0xffffff,
        quality: 0.5,
      }),
    ])
    dot.setOrientation(direction ?? 'north')
    // const dot = new Dot(this.ticker, this.scale)
    dot.alpha = 0.1
    dot.interactive = true
    dot.on('pointerdown', () => {
      this.emit(`game:board-${side}-action-click`, direction && { direction, x, y })
      this.cleanInteractions()
    })
    dot.on('pointerover', () => {
      dot.alpha = 0.2
    })
    dot.on('pointerout', () => {
      dot.alpha = 0.1
    })
    dot.setPosition(this.scaleX(x), this.scaleY(y))
    dot.addTo(this.interactionContainer)
  }

  getOrientation(tile: TileDto, side: string) {
    let orientation = ''
    const isPair = isTilePair(tile)
    const isLeft = side === 'left'
    const endTile = isLeft ? this.leftTile : this.rightTile

    if (side === 'left') {
      if (this.freeEnds !== undefined && tile.pips !== undefined) {
        const isInverted = this.freeEnds[0] === tile.pips[1]
        if (this.leftDirection === 'east') {
          if (!isPair) {
            orientation = isInverted ? 'east' : 'west'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.leftDirection === 'west') {
          if (!isPair) {
            orientation = isInverted ? 'west' : 'east'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.leftDirection === 'north') {
          if (!isPair) {
            orientation = isInverted ? 'north' : 'south'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.leftDirection === 'south') {
          if (!isPair) {
            orientation = isInverted ? 'south' : 'north'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        }
      }
    } else if (side === 'right') {
      if (this.freeEnds !== undefined && tile.pips !== undefined) {
        const isInverted = this.freeEnds[1] === tile.pips[0]
        if (this.rightDirection === 'east') {
          if (!isPair) {
            orientation = isInverted ? 'west' : 'east'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.rightDirection === 'west') {
          if (!isPair) {
            orientation = isInverted ? 'east' : 'west'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.rightDirection === 'north') {
          if (!isPair) {
            orientation = isInverted ? 'south' : 'north'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        } else if (this.rightDirection === 'south') {
          if (!isPair) {
            orientation = isInverted ? 'north' : 'south'
          } else {
            endTile?.isVertical() ? (orientation = 'west') : (orientation = 'north')
          }
        }
      }
    }
    return orientation
  }

  nextTileValidMoves(tile: TileDto, side: string): boolean[] {
    if (tile === undefined || this.freeEnds === undefined) return [false, false, false, false]
    if (this.count === 0) return [false, true, false, true] // depends on game mode

    const isLeft = side === 'left'
    const end = isLeft ? this.freeEnds[0] : this.freeEnds[1]
    const endTile = isLeft ? this.leftTile : this.rightTile
    const direction = isLeft ? this.leftDirection : this.rightDirection
    const signX = isLeft ? -1 : 1
    const signY = direction === 'south' ? 1 : -1
    const validMove = tile.pips?.includes(end) ?? false
    const endX = endTile?.x ?? 0
    const endY = endTile?.y ?? 0
    const tileHeight = endTile?.height ?? 0
    const margin = 20
    const spaceNeeded = tileHeight / 2 + tileHeight + margin
    const isSecond = this.count === 1
    const isPair = isTilePair(tile)
    const hasSpaceEast = endX + signX * spaceNeeded < this.width
    const hasSpaceWest = endX + signX * spaceNeeded > 0
    const hasSpaceNorth = endY + signY * spaceNeeded > 0
    const hasSpaceSouth = endY + signY * spaceNeeded < this.height

    let canPlayNorth = false
    let canPlaySouth = false
    let canPlayEast = false
    let canPlayWest = false

    if (validMove) {
      canPlayEast = direction !== 'west' && hasSpaceEast
      canPlayWest = direction !== 'east' && hasSpaceWest
      canPlayNorth = direction !== 'south' && hasSpaceNorth
      canPlaySouth = direction !== 'north' && hasSpaceSouth
    }

    if (isSecond) {
      canPlayNorth = canPlaySouth = false
    }

    if (isPair && !endTile?.isVertical()) {
      if ((direction === 'east' && hasSpaceEast) || (direction === 'west' && hasSpaceWest)) {
        canPlayNorth = canPlaySouth = false
      }
    }

    return [canPlayNorth, canPlayEast, canPlaySouth, canPlayWest]
  }

  hasSpaceToMove(move: Movement): string | undefined {
    if (move.tile === undefined || move.direction === undefined) {
      return undefined
    }

    const nextValidMoves = this.nextTileValidMoves(move.tile, move.type)
    let index = DIRECTION_INDEXES[move.direction]
    let valid = nextValidMoves[index]
    while (!valid && index < nextValidMoves.length) {
      index++
      valid = nextValidMoves[index % nextValidMoves.length]
    }

    return DIRECTIONS[index]
  }

  clean() {
    this.tiles = []
    this.boneyard = []
    this.movements = []
    this.playerHand = []
    this.freeEnds = undefined
    this.leftTile = undefined
    this.rightTile = undefined
    this.nextTile = undefined
    this.leftDirection = 'west'
    this.rightDirection = 'east'
    this.firstTile = undefined
    this.tilesContainer.removeChildren()
    this.interactionContainer.removeChildren()
  }

  gameFinished() {
    this.showText(t('game.round-finished'))
  }

  matchFinished() {
    this.showText(t('game.match-finished'))
  }
}
