import Phaser from 'phaser'
import type { MovementVector } from '../../../../input'
import { IWT2_BOSS_METADATA } from '../../content/bosses'
import { IWT2_CLASS_METADATA } from '../../content/classes'
import type {
  Iwt2ArenaIndicator,
  Iwt2ArenaState,
  Iwt2BossEntityState,
  Iwt2GroundHazardState,
  Iwt2HostileAddState,
  Iwt2PartyEntityState,
  Iwt2Vec2,
} from '../../sim'

type SceneDeps = {
  getMovement: () => MovementVector
  getSelectedPartyId: () => string
  getState: () => Iwt2ArenaState
  step: (movement: MovementVector, dtSeconds: number) => Iwt2ArenaState
}

type DrawableEntity = Iwt2PartyEntityState | Iwt2BossEntityState | Iwt2HostileAddState

export class BulldromeArenaScene extends Phaser.Scene {
  private deps: SceneDeps
  private arenaGraphics?: Phaser.GameObjects.Graphics
  private entityGraphics?: Phaser.GameObjects.Graphics
  private telegraphGraphics?: Phaser.GameObjects.Graphics
  private labels = new Map<string, Phaser.GameObjects.Text>()
  private lastHudPublish = 0

  constructor(deps: SceneDeps) {
    super('bulldrome-arena')
    this.deps = deps
  }

  create() {
    this.cameras.main.setBackgroundColor('#10141b')
    this.arenaGraphics = this.add.graphics()
    this.telegraphGraphics = this.add.graphics()
    this.entityGraphics = this.add.graphics()
    this.drawState(this.deps.getState())
  }

  update(_: number, delta: number) {
    const state = this.deps.step(this.deps.getMovement(), Math.min(0.05, delta / 1000))
    this.drawState(state)
  }

  private drawState(state: Iwt2ArenaState) {
    if (!this.arenaGraphics || !this.entityGraphics || !this.telegraphGraphics) return
    this.drawArena(state)
    this.drawTelegraphs(state)
    this.drawEntities(state)
    this.drawProjectiles(state)
    this.lastHudPublish += 1
  }

  private drawArena(state: Iwt2ArenaState) {
    const graphics = this.arenaGraphics!
    graphics.clear()
    graphics.lineStyle(3, 0x3c4a59, 1)
    graphics.fillStyle(0x171d24, 1)
    graphics.fillRoundedRect(0, 0, state.bounds.width, state.bounds.height, 12)
    graphics.strokeRoundedRect(0, 0, state.bounds.width, state.bounds.height, 12)

    graphics.lineStyle(1, 0x26313d, 0.55)
    for (let x = 80; x < state.bounds.width; x += 80) {
      graphics.lineBetween(x, 0, x, state.bounds.height)
    }
    for (let y = 80; y < state.bounds.height; y += 80) {
      graphics.lineBetween(0, y, state.bounds.width, y)
    }
  }

  private drawTelegraphs(state: Iwt2ArenaState) {
    const graphics = this.telegraphGraphics!
    graphics.clear()
    state.indicators.forEach((indicator) => drawIndicator(graphics, indicator))
  }

  private drawEntities(state: Iwt2ArenaState) {
    const graphics = this.entityGraphics!
    graphics.clear()
    for (const hazard of state.hazards) drawHazard(graphics, hazard)
    const entities: DrawableEntity[] = [...state.party, ...state.hostileAdds, ...state.bosses]
    const liveIds = new Set<string>(entities.map((entity) => entity.id))

    for (const entity of entities.sort(entitySort)) {
      const stunned = entity.kind === 'party' && (entity.status.stunnedSeconds > 0 || entity.status.knockedDownSeconds > 0)
      const alpha = entity.health <= 0 ? 0.35 : stunned ? 0.55 : 1
      const color = entityColor(entity)
      graphics.fillStyle(Number.parseInt(color.slice(1), 16), alpha)
      graphics.lineStyle(entity.kind === 'party' && entity.aiRole === 'player' ? 4 : 2, entity.kind === 'boss' ? 0xfff0b8 : 0x0a0c10, 1)
      if (entity.kind === 'boss') {
        graphics.fillEllipse(entity.position.x, entity.position.y, entity.radius * 2.4, entity.radius * 1.75)
        graphics.strokeEllipse(entity.position.x, entity.position.y, entity.radius * 2.4, entity.radius * 1.75)
      } else if (entity.kind === 'hostileAdd') {
        const wing = entity.radius * 1.55
        graphics.fillTriangle(
          entity.position.x + entity.facing.x * wing,
          entity.position.y,
          entity.position.x - entity.facing.x * entity.radius,
          entity.position.y - entity.radius,
          entity.position.x - entity.facing.x * entity.radius,
          entity.position.y + entity.radius,
        )
        graphics.strokeTriangle(
          entity.position.x + entity.facing.x * wing,
          entity.position.y,
          entity.position.x - entity.facing.x * entity.radius,
          entity.position.y - entity.radius,
          entity.position.x - entity.facing.x * entity.radius,
          entity.position.y + entity.radius,
        )
      } else {
        if (entity.id === this.deps.getSelectedPartyId()) {
          graphics.lineStyle(4, 0xfff4a8, 0.95)
          graphics.strokeCircle(entity.position.x, entity.position.y, entity.radius + 8)
        }
        graphics.fillCircle(entity.position.x, entity.position.y, entity.radius)
        graphics.strokeCircle(entity.position.x, entity.position.y, entity.radius)
      }
      this.drawLabel(entity)
      this.drawHealthBar(graphics, entity)
    }

    for (const [id, label] of this.labels) {
      if (!liveIds.has(id)) {
        label.destroy()
        this.labels.delete(id)
      }
    }
  }

  private drawProjectiles(state: Iwt2ArenaState) {
    const graphics = this.entityGraphics!
    for (const projectile of state.projectiles) {
      const color = Number.parseInt(projectile.color.slice(1), 16)
      graphics.fillStyle(color, 0.95)
      graphics.lineStyle(projectile.projectileKind === 'magic' || projectile.projectileKind === 'fireball' ? 3 : 2, color, 0.75)
      graphics.strokeCircle(projectile.position.x, projectile.position.y, projectile.radius + 3)
      graphics.fillCircle(projectile.position.x, projectile.position.y, projectile.radius)
    }
  }

  private drawLabel(entity: DrawableEntity) {
    let label = this.labels.get(entity.id)
    const icon = entityIcon(entity)
    if (!label) {
      label = this.add.text(entity.position.x, entity.position.y, icon, {
        align: 'center',
        color: '#fff7df',
        fontFamily: 'ui-monospace, Consolas, monospace',
        fontSize: entity.kind === 'boss' ? '20px' : entity.kind === 'hostileAdd' ? '13px' : '15px',
        fontStyle: '900',
      }).setOrigin(0.5)
      this.labels.set(entity.id, label)
    }
    const stunned = entity.kind === 'party' && (entity.status.stunnedSeconds > 0 || entity.status.knockedDownSeconds > 0)
    label.setText(stunned ? '!' : icon)
    label.setPosition(entity.position.x, entity.position.y - (entity.kind === 'boss' ? 1 : 0))
    label.setAlpha(entity.health <= 0 ? 0.35 : 1)
  }

  private drawHealthBar(graphics: Phaser.GameObjects.Graphics, entity: DrawableEntity) {
    const width = entity.kind === 'boss' ? 92 : 34
    const height = entity.kind === 'boss' ? 8 : 7
    const y = entity.position.y + entity.radius + 8
    const ratio = Math.max(0, Math.min(1, entity.health / entity.maxHealth))
    graphics.fillStyle(0x050608, 0.9)
    graphics.fillRect(entity.position.x - width / 2, y, width, height)
    graphics.fillStyle(ratio > 0.5 ? 0x4ac77d : ratio > 0.25 ? 0xf1c663 : 0xf05b4f, 1)
    graphics.fillRect(entity.position.x - width / 2, y, width * ratio, height)
    if (entity.kind === 'party' && entity.shield > 0) {
      const shieldRatio = Math.max(0, Math.min(1, entity.shield / entity.maxHealth))
      graphics.fillStyle(0x58a8ff, 0.92)
      graphics.fillRect(entity.position.x + width / 2 - width * shieldRatio, y, width * shieldRatio, height)
    }
  }
}

function entitySort(a: DrawableEntity, b: DrawableEntity): number {
  const rank = { boss: 0, hostileAdd: 1, party: 2 } satisfies Record<DrawableEntity['kind'], number>
  return rank[a.kind] - rank[b.kind]
}

function entityColor(entity: DrawableEntity): string {
  if (entity.kind === 'boss') return IWT2_BOSS_METADATA[entity.bossId].color
  if (entity.kind === 'hostileAdd') return '#f0b84f'
  return IWT2_CLASS_METADATA[entity.classId].color
}

function entityIcon(entity: DrawableEntity): string {
  if (entity.kind === 'boss') return IWT2_BOSS_METADATA[entity.bossId].icon
  if (entity.kind === 'hostileAdd') return 'v'
  return IWT2_CLASS_METADATA[entity.classId].icon
}

function drawHazard(graphics: Phaser.GameObjects.Graphics, hazard: Iwt2GroundHazardState) {
  const ratio = Math.max(0, Math.min(1, hazard.remainingSeconds / Math.max(0.001, hazard.fadeSeconds)))
  const alpha = hazard.remainingSeconds <= hazard.fadeSeconds ? 0.16 + ratio * 0.2 : 0.34
  graphics.fillStyle(0xff7a2f, alpha)
  graphics.lineStyle(2, 0xffc15a, 0.55)
  graphics.fillCircle(hazard.position.x, hazard.position.y, hazard.radius)
  graphics.strokeCircle(hazard.position.x, hazard.position.y, hazard.radius)
}

function drawIndicator(graphics: Phaser.GameObjects.Graphics, indicator: Iwt2ArenaIndicator) {
  const color = Number.parseInt(indicator.color.slice(1), 16)
  const active = indicator.phase === 'active'
  graphics.lineStyle(active ? 8 : 5, color, indicator.lineAlpha ?? (active ? 0.76 : 0.5))
  graphics.fillStyle(color, indicator.fillAlpha ?? (active ? 0.16 : 0.09))

  if (indicator.kind === 'lane') {
    const points = laneDangerPolygon(indicator.start, indicator.end, indicator.width)
    graphics.fillPoints(points, true)
    graphics.strokePoints(points, true)
    return
  }

  if (indicator.kind === 'circle') {
    graphics.strokeCircle(indicator.position.x, indicator.position.y, indicator.radius)
    graphics.fillCircle(indicator.position.x, indicator.position.y, indicator.radius)
    return
  }

  if (indicator.kind === 'cone') {
    const points = coneDangerPolygon(
      indicator.origin,
      indicator.direction,
      indicator.range,
      indicator.angleRadians,
    )
    graphics.fillPoints(points, true)
    graphics.strokePoints(points, true)
    return
  }

  graphics.strokeCircle(indicator.position.x, indicator.position.y, indicator.outerRadius)
  graphics.fillCircle(indicator.position.x, indicator.position.y, indicator.outerRadius)
  graphics.lineStyle(active ? 5 : 3, color, indicator.lineAlpha ?? 0.6)
  graphics.strokeCircle(indicator.position.x, indicator.position.y, indicator.innerRadius)
}

function laneDangerPolygon(start: Iwt2Vec2, end: Iwt2Vec2, halfWidth: number) {
  const dx = end.x - start.x
  const dy = end.y - start.y
  const length = Math.max(1, Math.hypot(dx, dy))
  const normal = {
    x: (-dy / length) * halfWidth,
    y: (dx / length) * halfWidth,
  }
  return [
    new Phaser.Math.Vector2(start.x + normal.x, start.y + normal.y),
    new Phaser.Math.Vector2(end.x + normal.x, end.y + normal.y),
    new Phaser.Math.Vector2(end.x - normal.x, end.y - normal.y),
    new Phaser.Math.Vector2(start.x - normal.x, start.y - normal.y),
  ]
}

function coneDangerPolygon(
  origin: Iwt2Vec2,
  direction: Iwt2Vec2,
  range: number,
  angleRadians: number,
) {
  const baseAngle = Math.atan2(direction.y, direction.x)
  const halfAngle = angleRadians / 2
  const points = [new Phaser.Math.Vector2(origin.x, origin.y)]
  const segmentCount = 14
  for (let index = 0; index <= segmentCount; index += 1) {
    const t = index / segmentCount
    const angle = baseAngle - halfAngle + angleRadians * t
    points.push(new Phaser.Math.Vector2(
      origin.x + Math.cos(angle) * range,
      origin.y + Math.sin(angle) * range,
    ))
  }
  return points
}
