/**
 * Lane J — "can paint stick to this?" report for the blob body slot.
 *
 * Mirrors Lane I's `paintableInfo` (lanes/LANE-I §Deliverables 2) so integration
 * can collapse the two. The checks exist because PaintSkin stamps into UV space
 * through a raycast: no UV attribute means no stamps at all, UVs outside 0..1
 * wrap into unrelated parts of the texture, and more than one material means
 * PaintSkin's `mat.map = tex` lands on only one of them (the blob then reads as
 * unpainted while coverage still climbs — a silent, maddening divergence).
 */
import * as THREE from 'three'

export interface PaintableInfo {
  /** A UV attribute exists and stays inside 0..1 (with a hair of tolerance). */
  uvOk: boolean
  triCount: number
  materialCount: number
  /** False when nothing mesh-like was found at all. */
  hasMesh: boolean
  /** Plain-language reasons the report is unhappy; empty when all good. */
  problems: string[]
}

const UV_TOLERANCE = 0.001

/** The mesh PaintSkin would end up bound to: the first Mesh in the subtree. */
export function firstMesh(object: THREE.Object3D): THREE.Mesh | null {
  let found: THREE.Mesh | null = null
  object.traverse((o) => {
    if (!found && (o as THREE.Mesh).isMesh) found = o as THREE.Mesh
  })
  return found
}

export function inspectPaintability(object: THREE.Object3D): PaintableInfo {
  const mesh = firstMesh(object)
  if (!mesh) {
    return {
      uvOk: false, triCount: 0, materialCount: 0, hasMesh: false,
      problems: ['This file has no model in it that paint could stick to.'],
    }
  }

  const geo = mesh.geometry
  const problems: string[] = []
  const uv = geo.getAttribute('uv') as THREE.BufferAttribute | undefined

  let uvOk = false
  if (!uv) {
    problems.push('No UV map — paint has nowhere to land. Unwrap the model in Blender.')
  } else {
    let min = Infinity
    let max = -Infinity
    for (let i = 0; i < uv.count; i++) {
      const u = uv.getX(i)
      const v = uv.getY(i)
      if (u < min) min = u
      if (v < min) min = v
      if (u > max) max = u
      if (v > max) max = v
    }
    uvOk = min >= -UV_TOLERANCE && max <= 1 + UV_TOLERANCE
    if (!uvOk) {
      problems.push(
        `UV map runs outside the square (${min.toFixed(2)} to ${max.toFixed(2)}) — ` +
        'splats will show up in the wrong places. Pack the UVs into 0–1.',
      )
    }
  }

  const index = geo.getIndex()
  const position = geo.getAttribute('position') as THREE.BufferAttribute | undefined
  const triCount = Math.floor((index ? index.count : (position?.count ?? 0)) / 3)

  const materialCount = Array.isArray(mesh.material) ? mesh.material.length : 1
  if (materialCount > 1) {
    problems.push(
      `${materialCount} materials on the body — paint only ever shows on one. ` +
      'Join it down to a single material in Blender.',
    )
  }
  const single = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material
  if (single && !(single as THREE.MeshStandardMaterial).isMeshStandardMaterial) {
    problems.push('The body material is an unusual type — export it as a Principled BSDF.')
  }

  return { uvOk, triCount, materialCount, hasMesh: true, problems }
}
