import {
  Application,
  Container,
  EventEmitter,
  Graphics,
  Point,
  Sprite,
  Texture,
  Ticker,
} from 'pixi.js'
import { Tile } from '@/game/Tile'
import type { MatchSessionOptions, PlayerDto, TileDto } from '@/common/interfaces'
import { GlowFilter } from 'pixi-filters'
import { Scale, type ScaleFunction } from './utilities/scale'
import { LoggingService } from '@/services/LoggingService'
import { createContainer } from '@/common/helpers'
import { createText, playerNameText, whiteStyle } from './utilities/fonts'
import Config from '@/game/Config'
import { TimerText } from './TimerText'
import { Button } from './Button'

export class Hand extends EventEmitter {
  tiles: Tile[] = []
  container: Container = new Container()
  buttonPass!: Button
  scoreLayer: Container = new Container()
  activeLayer: Container = new Container()
  height: number
  width: number
  ticker: Ticker
  lastTimeClicked: number = 0
  doubleClickThreshold: number = 300
  initialized: boolean = false
  _canMove: boolean = false
  scale: number = 1
  scaleY!: ScaleFunction
  scaleX!: ScaleFunction
  grain: number = 25
  logger: LoggingService = new LoggingService()
  availableTiles: Tile[] = []
  tilesLayer!: Container
  interactionsLayer!: Container
  score: number = 0
  active: boolean = false
  private player!: PlayerDto
  private timer: TimerText

  constructor(
    app: Application,
    private options: MatchSessionOptions,
  ) {
    super()
    app.stage.addChild(this.container)
    this.ticker = app.ticker
    this.height = 130 * this.scale
    this.width = 800 // app.canvas.width
    this.container.y = app.canvas.height - this.height
    this.container.x = app.canvas.width / 2 - this.width / 2
    this.container.width = this.width
    this.container.height = this.height
    this.buttonPass = this.createPassButton()
    this.timer = this.createTimer(this.options.turnWaitSeconds)
    this.calculateScale()
    this.initLayers()
    this.render()
  }

  initLayers() {
    this.container.removeChildren()
    this.addBg()
    this.tilesLayer = createContainer({
      width: this.width,
      height: this.height,
      x: 0,
      y: 0,
      parent: this.container,
    })
    this.interactionsLayer = createContainer({
      width: this.width,
      height: this.height,
      x: 0,
      y: 0,
      parent: this.container,
    })
    this.container.addChild(this.scoreLayer)
    this.container.addChild(this.activeLayer)
    this.interactionsLayer.addChild(this.buttonPass)
    this.interactionsLayer.addChild(this.timer)
  }

  gameFinished() {
    this.tiles = []
    this.initialized = false
  }

  get canMove() {
    return this._canMove
  }

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

  prepareForMove(isFirstMove: boolean, freeEnds?: [number, number]) {
    this.availableTiles = isFirstMove
      ? this.tiles
      : this.tiles.filter((tile) => this.hasMoves(tile.toPlain(), freeEnds))
    if (this.availableTiles.length === 0) {
      this.buttonPass.disabled = false
    }
    this.availableTiles.forEach((tile) => {
      tile.animateTo({
        x: tile.x,
        y: tile.y - 10,
      })
      tile.interactive = true
    })
    if (this.timer) {
      this.timer.reset()
      this.timer.start()
    }
  }

  createTimer(seconds: number): TimerText {
    const timer = new TimerText(seconds, new Point(this.width - 60, 45))
    // timer.animation = false
    timer.on('timeout', () => {
      this.timerTimeout()
    })
    return timer
  }

  timerTimeout() {
    if (this.timer === undefined) return
    this.timer.reset()
    if (this.availableTiles.length === 0) {
      this.emit('game:timer-timeout', null)
      return
    }
    const randomTile = this.availableTiles[Math.floor(Math.random() * this.availableTiles.length)] // pick random tile
    randomTile.alpha = 1
    this.emit('game:timer-timeout', randomTile.toPlain())
  }

  afterMove() {
    this.availableTiles.forEach((tile) => {
      tile.animateTo({
        x: tile.x,
        y: tile.y + 10,
      })
      tile.setPosition(tile.x, tile.y + 10)
      tile.interactive = false
    })
    this.timer && this.timer.reset()
  }

  hasMoves(tile: TileDto, freeEnds?: [number, number]): boolean {
    if (tile === undefined || freeEnds === undefined) return false

    let hasMoves: boolean = false
    if (tile.pips != undefined) {
      hasMoves = tile.pips.includes(freeEnds[0]) || tile.pips.includes(freeEnds[1])
    }
    return hasMoves
  }

  initialize(playerState: PlayerDto) {
    this.tiles = this.createTiles(playerState)
    this.initialized = this.tiles.length > 0
    this.render()
    this.emit('hand-updated', this.tiles)
  }

  private addBg() {
    const bg = new Sprite(Texture.WHITE)
    bg.alpha = 0.08
    bg.width = this.width
    bg.height = this.height
    this.container.addChild(bg)
  }

  private onTileClick(tile: Tile) {
    // if (Date.now() - this.lastTimeClicked < this.doubleClickThreshold) {
    //   this.emit('tileDoubleClick', { id: tile.id })
    //   return
    // }

    const selected = this.tiles.find((t) => t.selected)
    if (selected) {
      this.deselectTile(selected)

      if (selected.id === tile.id) {
        this.emit('game:tile-click')
        return
      }
    }

    tile.selected = true
    tile.alpha = 1

    this.emit('game:tile-click', tile.toPlain())
  }

  private deselectTile(selected: Tile) {
    selected.selected = false
    selected.alpha = 0.7
  }

  public tileMoved(tileDto: TileDto): Tile | undefined {
    const tile = this.tiles.find((t) => t.id === tileDto.id)

    if (!tile) return

    this.afterMove()
    this.tiles = this.tiles.filter((t) => t.id !== tileDto.id)

    tile.interactive = false
    tile.clearFilters()
    tile.off('pointerdown')
    tile.off('pointerover')
    tile.off('pointerout')
    this.tilesLayer.removeChild(tile.getSprite())
    return tile
  }

  private createPassButton(): Button {
    const x = this.width - 100
    const y = this.height - 45
    const btn = new Button('PASS', new Point(x, y), { width: 80, height: 30 }, () => {
      this.timer.reset()
      this.buttonPass.disabled = true
      this.emit('game:button-pass-click')
    })
    btn.disabledColor = 0xbabf95
    btn.disabled = true
    this.buttonPass = btn
    return btn
  }

  disablePassButton() {
    this.buttonPass.disabled = true
  }

  update(playerState: PlayerDto) {
    this.tilesLayer.removeChildren()
    if (!this.initialized) {
      this.initialize(playerState)
      return
    }
    const missing: Tile | undefined = this.tiles.find(
      (tile: Tile) => !playerState.hand.find((t) => t.id === tile.id),
    )
    if (missing) {
      this.tilesLayer.removeChild(missing.getSprite())
      this.tiles = this.tiles.filter((tile) => tile.id !== missing.id)
      this.emit('hand-updated', this.tiles)
    }
    this.render()
  }

  setPlayer(player: PlayerDto | undefined) {
    if (!player) return
    this.player = player
    this.render()
  }

  setScore(score: number) {
    this.score = score
    this.render()
  }

  setActive(active: boolean) {
    this.active = active
    this.render()
  }

  private createTiles(playerState: PlayerDto) {
    return playerState.hand.map((tile: TileDto) => {
      const newTile: Tile = new Tile(tile.id, this.ticker, tile.pips, this.scale, tile.playerId)
      newTile.alpha = 0.7
      newTile.anchor = 0.5
      newTile.addTo(this.tilesLayer)
      newTile.on('pointerdown', () => this.onTileClick(newTile))
      newTile.on('pointerover', () => {
        this.emit('tileHover', newTile.toPlain())
        newTile.alpha = 1
        newTile.setFilters([
          new GlowFilter({
            distance: 10,
            outerStrength: 2,
            innerStrength: 1,
            color: 0xffffff,
            quality: 0.5,
          }),
        ])
      })
      newTile.on('pointerout', () => {
        if (!newTile.selected) {
          this.emit('tileHover')
          newTile.alpha = 0.7
          newTile.getSprite().filters = []
        }
      })
      return newTile
    })
  }

  renderTiles() {
    const deltaX = this.width / 2 - (this.tiles.length * 50 - 5) / 2
    this.tiles.forEach((tile, i) => {
      tile.setPosition(deltaX + tile.width / 2 + i * (tile.width + 5), tile.height / 2 + 20)
    })
  }

  renderScore() {
    this.scoreLayer.removeChildren()
    const name = createText({
      text: this.player?.name ?? '-',
      x: 100,
      y: 50,
      style: playerNameText,
    })
    const text = createText({
      text: `${this.score}`,
      x: 100,
      // x: this.width - 5,
      y: 80,
      style: whiteStyle(36, 'bold'),
    })
    text.anchor.set(1, 0.5)
    this.scoreLayer.addChild(name)
    this.scoreLayer.addChild(text)
  }

  renderActive() {
    this.activeLayer.removeChildren()
    if (this.active) {
      const rectangle = new Graphics()
        .roundRect(0, 0, this.width, this.height - 1, 5)
        .stroke(Config.activeHandStrokeColor)
      this.activeLayer.addChild(rectangle)
    }
  }

  render() {
    this.renderTiles()
    this.renderScore()
    this.renderActive()
  }
}
