type CellKey = `${number},${number}`

interface GridCell {
  itemIds: Set<string>
}

interface SpatialGridConfig {
  cellSize: number // e.g., 0.5 meters = Sims-style half-tile
}

export class SpatialGrid {
  private readonly cells = new Map<CellKey, GridCell>()
  private readonly itemCells = new Map<string, Set<CellKey>>() // reverse lookup

  private readonly config: SpatialGridConfig

  constructor(config: SpatialGridConfig) {
    this.config = config
  }

  private posToCell(x: number, z: number): [number, number] {
    return [Math.floor(x / this.config.cellSize), Math.floor(z / this.config.cellSize)]
  }

  private cellKey(cx: number, cz: number): CellKey {
    return `${cx},${cz}`
  }

  // Get all cells an item occupies based on its AABB
  private getItemCells(
    position: [number, number, number],
    dimensions: [number, number, number],
    rotation: [number, number, number],
  ): CellKey[] {
    // Simplified: axis-aligned bounding box
    // For full rotation support, compute rotated corners
    const [x, , z] = position
    const [w, , d] = dimensions
    const yRot = rotation[1] // Y-axis rotation

    // Compute rotated footprint (simplified for 90° increments)
    const cos = Math.abs(Math.cos(yRot))
    const sin = Math.abs(Math.sin(yRot))
    const rotatedW = w * cos + d * sin
    const rotatedD = w * sin + d * cos

    const minX = x - rotatedW / 2
    const maxX = x + rotatedW / 2
    const minZ = z - rotatedD / 2
    const maxZ = z + rotatedD / 2

    const [minCx, minCz] = this.posToCell(minX, minZ)
    // Use exclusive upper bound: subtract epsilon so exact boundaries don't overlap
    // This allows adjacent items (touching but not overlapping) to not conflict
    const epsilon = 1e-6
    const [maxCx, maxCz] = this.posToCell(maxX - epsilon, maxZ - epsilon)

    const keys: CellKey[] = []
    for (let cx = minCx; cx <= maxCx; cx++) {
      for (let cz = minCz; cz <= maxCz; cz++) {
        keys.push(this.cellKey(cx, cz))
      }
    }
    return keys
  }

  // Register an item
  insert(
    itemId: string,
    position: [number, number, number],
    dimensions: [number, number, number],
    rotation: [number, number, number],
  ) {
    const cellKeys = this.getItemCells(position, dimensions, rotation)

    this.itemCells.set(itemId, new Set(cellKeys))

    for (const key of cellKeys) {
      if (!this.cells.has(key)) {
        this.cells.set(key, { itemIds: new Set() })
      }
      this.cells.get(key)?.itemIds.add(itemId)
    }
  }

  // Remove an item
  remove(itemId: string) {
    const cellKeys = this.itemCells.get(itemId)
    if (!cellKeys) return

    for (const key of cellKeys) {
      const cell = this.cells.get(key)
      if (cell) {
        cell.itemIds.delete(itemId)
        if (cell.itemIds.size === 0) {
          this.cells.delete(key)
        }
      }
    }
    this.itemCells.delete(itemId)
  }

  // Update = remove + insert
  update(
    itemId: string,
    position: [number, number, number],
    dimensions: [number, number, number],
    rotation: [number, number, number],
  ) {
    this.remove(itemId)
    this.insert(itemId, position, dimensions, rotation)
  }

  // Query: is this placement valid?
  canPlace(
    position: [number, number, number],
    dimensions: [number, number, number],
    rotation: [number, number, number],
    ignoreIds: string[] = [],
  ): { valid: boolean; conflictIds: string[] } {
    const cellKeys = this.getItemCells(position, dimensions, rotation)
    const ignoreSet = new Set(ignoreIds)
    const conflicts = new Set<string>()

    for (const key of cellKeys) {
      const cell = this.cells.get(key)
      if (cell) {
        for (const id of cell.itemIds) {
          if (!ignoreSet.has(id)) {
            conflicts.add(id)
          }
        }
      }
    }

    return {
      valid: conflicts.size === 0,
      conflictIds: [...conflicts],
    }
  }

  // Query: get all items near a point (for snapping, selection, etc.)
  queryRadius(x: number, z: number, radius: number): string[] {
    const cellRadius = Math.ceil(radius / this.config.cellSize)
    const [cx, cz] = this.posToCell(x, z)
    const found = new Set<string>()

    for (let dx = -cellRadius; dx <= cellRadius; dx++) {
      for (let dz = -cellRadius; dz <= cellRadius; dz++) {
        const cell = this.cells.get(this.cellKey(cx + dx, cz + dz))
        if (cell) {
          for (const id of cell.itemIds) {
            found.add(id)
          }
        }
      }
    }
    return [...found]
  }

  getItemCount(): number {
    return this.itemCells.size
  }
}
