/**
 * BuffSystem — every fixed step, reads the blob's coverage() and writes
 * blob.modifiers per GDD §6 (MVP subset), then drives the two cheap visual
 * side-effects that make the buff READ on the body: an ember trail while the RED
 * BURN buff is active, and an emissive glow pulse in the super state.
 *
 * The number-crunching lives in computeModifiers() (coverage-math.ts, pure &
 * unit-tested); this System just applies it and paints the juice.
 */
import * as THREE from 'three'
import type { BlobModifiers, System, PaintColor } from '../contracts'
import { PALETTE } from '../contracts'
import type { PaintSkin } from './skin'
import {
  ACTIVATE,
  buffStrength,
  computeModifiers,
  dominantColor,
  superColor,
} from './coverage-math'

export interface BuffTarget {
  mesh: THREE.Mesh
  modifiers: BlobModifiers
  paint?: PaintSkin
}

const EMBER_COUNT = 90

export class BuffSystem implements System {
  private readonly scene: THREE.Scene
  private readonly blob: BuffTarget

  // ember pool
  private readonly embers: THREE.Points
  private readonly emberPos: Float32Array
  private readonly emberCol: Float32Array
  private readonly emberVel: Float32Array
  private readonly emberLife: Float32Array
  private emberCursor = 0
  private readonly emberHex = new THREE.Color()
  private readonly _spawnAt = new THREE.Vector3()

  private clock = 0

  constructor(world: { scene: THREE.Scene }, blob: BuffTarget) {
    this.scene = world.scene
    this.blob = blob
    // ember tint: red→orange blend, computed once
    this.emberHex.set(PALETTE.red).lerp(new THREE.Color(PALETTE.orange), 0.5)

    this.emberPos = new Float32Array(EMBER_COUNT * 3)
    this.emberCol = new Float32Array(EMBER_COUNT * 3)
    this.emberVel = new Float32Array(EMBER_COUNT * 3)
    this.emberLife = new Float32Array(EMBER_COUNT) // 0 = dead
    const geo = new THREE.BufferGeometry()
    geo.setAttribute('position', new THREE.BufferAttribute(this.emberPos, 3))
    geo.setAttribute('color', new THREE.BufferAttribute(this.emberCol, 3))
    const mat = new THREE.PointsMaterial({
      size: 0.16,
      vertexColors: true,
      transparent: true,
      depthWrite: false,
      blending: THREE.AdditiveBlending,
      sizeAttenuation: true,
    })
    this.embers = new THREE.Points(geo, mat)
    this.embers.frustumCulled = false
    this.scene.add(this.embers)
  }

  update(dt: number): void {
    this.clock += dt
    const paint = this.blob.paint
    if (!paint) return

    const cov = paint.coverage() // 250ms-cached; no per-frame readback
    const mods = computeModifiers(cov)
    // Copy into the shared modifiers object (readers hold this reference).
    // NOTE: `size` + `massMul` now also carry the purple MEGA / pink MINI scale
    // buffs (Lane G) straight out of computeModifiers — the controller reads
    // `size` to scale the collider + visuals, so no extra work is needed here.
    const m = this.blob.modifiers
    m.speedMul = mods.speedMul
    m.jumpMul = mods.jumpMul
    m.massMul = mods.massMul
    m.grip = mods.grip
    m.size = mods.size
    m.waterproof = mods.waterproof
    m.glow = mods.glow

    this.updateEmbers(dt, cov.byColor.red)
    this.updateGlow(mods.glow, superColor(cov) ?? dominantColor(cov))
  }

  dispose(): void {
    this.scene.remove(this.embers)
    this.embers.geometry.dispose()
    ;(this.embers.material as THREE.Material).dispose()
  }

  // ---- ember trail (RED BURN) ---------------------------------------------

  private updateEmbers(dt: number, red: number): void {
    const strength = buffStrength(red) // 0 below 20%
    if (red >= ACTIVATE) {
      // spawn rate scales with how hot the blob is
      const spawn = strength * 3
      let n = Math.floor(spawn) + (Math.random() < spawn % 1 ? 1 : 0)
      this.blob.mesh.getWorldPosition(this._spawnAt)
      while (n-- > 0) this.spawnEmber(this._spawnAt)
    }

    const emberHex = this.emberHex
    for (let i = 0; i < EMBER_COUNT; i++) {
      if (this.emberLife[i] <= 0) continue
      this.emberLife[i] -= dt
      const j = i * 3
      if (this.emberLife[i] <= 0) {
        this.emberCol[j] = this.emberCol[j + 1] = this.emberCol[j + 2] = 0 // invisible
        continue
      }
      this.emberVel[j + 1] += 2.2 * dt // buoyant rise
      this.emberPos[j] += this.emberVel[j] * dt
      this.emberPos[j + 1] += this.emberVel[j + 1] * dt
      this.emberPos[j + 2] += this.emberVel[j + 2] * dt
      const fade = Math.min(1, this.emberLife[i] / 0.5)
      this.emberCol[j] = emberHex.r * fade
      this.emberCol[j + 1] = emberHex.g * fade
      this.emberCol[j + 2] = emberHex.b * fade
    }
    this.embers.geometry.attributes.position.needsUpdate = true
    this.embers.geometry.attributes.color.needsUpdate = true
  }

  private spawnEmber(center: THREE.Vector3): void {
    const i = this.emberCursor
    this.emberCursor = (this.emberCursor + 1) % EMBER_COUNT
    const j = i * 3
    this.emberPos[j] = center.x + (Math.random() - 0.5) * 0.5
    this.emberPos[j + 1] = center.y + (Math.random() - 0.3) * 0.4
    this.emberPos[j + 2] = center.z + (Math.random() - 0.5) * 0.5
    this.emberVel[j] = (Math.random() - 0.5) * 0.8
    this.emberVel[j + 1] = 0.6 + Math.random() * 0.8
    this.emberVel[j + 2] = (Math.random() - 0.5) * 0.8
    this.emberLife[i] = 0.5 + Math.random() * 0.4
  }

  // ---- glow (super state) --------------------------------------------------

  private updateGlow(glow: number, color: PaintColor | null): void {
    const mat = this.blob.mesh.material as THREE.MeshStandardMaterial
    if (!('emissive' in mat)) return
    if (glow > 0 && color) {
      const pulse = 0.55 + 0.45 * Math.sin(this.clock * 8)
      mat.emissive.set(PALETTE[color])
      mat.emissiveIntensity = glow * pulse * 1.4
    } else {
      mat.emissiveIntensity = 0
    }
  }
}
