/**
 * PLY (Polygon File Format) decoder
 * Parses ASCII PLY files and extracts vertex positions and colors
 */
import { PlyReader } from '@sparkjsdev/spark'

interface PLYHeader {
  vertexCount: number
  hasColor: boolean
  propertyIndices: {
    x: number
    y: number
    z: number
    red: number
    green: number
    blue: number
  }
  headerEndLine: number
}

interface PLYData {
  positions: Float32Array
  colors: Float32Array | null
  vertexCount: number
}

function parsePLYHeader(lines: string[]): PLYHeader | null {
  let vertexCount = 0
  let headerEndLine = 0
  let hasColor = false
  let xIndex = -1
  let yIndex = -1
  let zIndex = -1
  let redIndex = -1
  let greenIndex = -1
  let blueIndex = -1
  let propertyIndex = 0

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim()

    if (line.startsWith('element vertex')) {
      vertexCount = parseInt(line.split(/\s+/)[2])
    } else if (line.startsWith('property')) {
      const parts = line.split(/\s+/)
      const propName = parts[parts.length - 1]

      if (propName === 'x') xIndex = propertyIndex
      else if (propName === 'y') yIndex = propertyIndex
      else if (propName === 'z') zIndex = propertyIndex
      else if (propName === 'red') {
        hasColor = true
        redIndex = propertyIndex
      } else if (propName === 'green') greenIndex = propertyIndex
      else if (propName === 'blue') blueIndex = propertyIndex

      propertyIndex++
    } else if (line === 'end_header') {
      headerEndLine = i
      break
    }
  }

  if (vertexCount === 0 || xIndex < 0 || yIndex < 0 || zIndex < 0) {
    return null
  }

  return {
    vertexCount,
    hasColor,
    propertyIndices: {
      x: xIndex,
      y: yIndex,
      z: zIndex,
      red: redIndex,
      green: greenIndex,
      blue: blueIndex
    },
    headerEndLine
  }
}

function parsePLYVertices(lines: string[], header: PLYHeader): PLYData {
  const { vertexCount, hasColor, propertyIndices, headerEndLine } = header
  const { x: xIndex, y: yIndex, z: zIndex } = propertyIndices
  const { red: redIndex, green: greenIndex, blue: blueIndex } = propertyIndices

  const positions = new Float32Array(vertexCount * 3)
  const colors = hasColor ? new Float32Array(vertexCount * 3) : null

  let vertexIndex = 0

  for (
    let i = headerEndLine + 1;
    i < lines.length && vertexIndex < vertexCount;
    i++
  ) {
    const line = lines[i].trim()
    if (!line) continue

    const parts = line.split(/\s+/)
    if (parts.length < 3) continue

    const posIndex = vertexIndex * 3

    positions[posIndex] = parseFloat(parts[xIndex])
    positions[posIndex + 1] = parseFloat(parts[yIndex])
    positions[posIndex + 2] = parseFloat(parts[zIndex])

    if (
      hasColor &&
      colors &&
      redIndex >= 0 &&
      greenIndex >= 0 &&
      blueIndex >= 0
    ) {
      if (parts.length > Math.max(redIndex, greenIndex, blueIndex)) {
        colors[posIndex] = parseInt(parts[redIndex]) / 255
        colors[posIndex + 1] = parseInt(parts[greenIndex]) / 255
        colors[posIndex + 2] = parseInt(parts[blueIndex]) / 255
      }
    }

    vertexIndex++
  }

  return {
    positions,
    colors,
    vertexCount: vertexIndex
  }
}

/**
 * Parse ASCII PLY data from an ArrayBuffer
 * Returns positions and colors as typed arrays
 */
export function parseASCIIPLY(arrayBuffer: ArrayBuffer): PLYData | null {
  const text = new TextDecoder().decode(arrayBuffer)
  const lines = text.split('\n')

  const header = parsePLYHeader(lines)
  if (!header) return null

  return parsePLYVertices(lines, header)
}

/**
 * Check if PLY data is in ASCII format
 */
export function isPLYAsciiFormat(arrayBuffer: ArrayBuffer): boolean {
  const header = new TextDecoder().decode(arrayBuffer.slice(0, 500))
  return header.includes('format ascii')
}

/**
 * Mirrors sparkjs's own check (PlyReader.parseSplats:
 * `hasScales && hasRots`), we delegate header parsing to sparkjs's
 * PlyReader so the property dictionary we inspect is exactly the one
 * sparkjs would see if asked to render the file. parseHeader rejects
 * ASCII PLYs by design; we catch and treat them as not-3DGS (no real
 * 3DGS export uses ASCII PLY).
 */
export async function isGaussianSplatPLY(
  arrayBuffer: ArrayBuffer
): Promise<boolean> {
  try {
    const reader = new PlyReader({ fileBytes: arrayBuffer })
    await reader.parseHeader()
    const props = reader.elements.vertex?.properties
    if (!props) return false
    const hasScales = !!(props.scale_0 && props.scale_1 && props.scale_2)
    const hasRots = !!(props.rot_0 && props.rot_1 && props.rot_2 && props.rot_3)
    return hasScales && hasRots
  } catch {
    return false
  }
}
