'use client'

import {
  type AnyNodeId,
  type CupolaNode,
  type RoofSegmentNode,
  useLiveNodeOverrides,
  useRegistry,
  useScene,
} from '@pascal-app/core'
import {
  type ColorPreset,
  createMaterial,
  createMaterialFromPresetRef,
  createSurfaceRoleMaterial,
  useNodeEvents,
  useViewer,
} from '@pascal-app/viewer'
import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
import { getAnalyticalNormal, surfaceQuatFromNormal } from '../shared/roof-surface'
import { buildCupolaGeometry } from './geometry'

const defaultMaterial = new THREE.MeshStandardMaterial({
  color: 0xff_ff_ff,
  roughness: 0.7,
  metalness: 0.2,
})

/**
 * Cupola renderer. Same transform stack as the box vent — the cupola is
 * parented to a roof-segment, so this reads the segment directly and
 * reproduces the segment-local transform (segment position → rotation →
 * cupola position → slope tilt → cupola yaw → mesh). No animation.
 */
const CupolaRenderer = ({ node: storeNode }: { node: CupolaNode }) => {
  const ref = useRef<THREE.Group>(null!)
  useRegistry(storeNode.id, 'cupola', ref)
  const handlers = useNodeEvents(storeNode, 'cupola')
  const shading = useViewer((s) => s.shading)
  const textures = useViewer((s) => s.textures)
  const colorPreset: ColorPreset = useViewer((s) => s.colorPreset)
  const sceneTheme = useViewer((s) => s.sceneTheme)

  const overrides = useLiveNodeOverrides(
    (s) => s.get(storeNode.id as AnyNodeId) as Partial<CupolaNode> | undefined,
  )
  const node: CupolaNode = overrides ? ({ ...storeNode, ...overrides } as CupolaNode) : storeNode

  const segment = useScene((state) =>
    node.roofSegmentId
      ? (state.nodes[node.roofSegmentId as AnyNodeId] as RoofSegmentNode | undefined)
      : undefined,
  )

  // biome-ignore lint/correctness/useExhaustiveDependencies: deps deliberately list the build inputs; depending on the whole object would rebuild on unrelated field changes.
  const geometry = useMemo(
    () => buildCupolaGeometry(node),
    [node.width, node.depth, node.height, node.roofStyle, node.finial],
  )
  useEffect(() => () => geometry.dispose(), [geometry])

  const surfaceQuat = useMemo(() => {
    if (!segment) return new THREE.Quaternion()
    const normal = getAnalyticalNormal(node.position[0] ?? 0, node.position[2] ?? 0, segment)
    return surfaceQuatFromNormal(normal, new THREE.Quaternion())
  }, [segment, node.position[0], node.position[2]])

  const material = useMemo(() => {
    if (!textures || (!node.material && !node.materialPreset)) {
      return createSurfaceRoleMaterial('roof', colorPreset, THREE.FrontSide, sceneTheme)
    }
    return node.material
      ? createMaterial(node.material, shading)
      : (createMaterialFromPresetRef(node.materialPreset, shading) ?? defaultMaterial)
  }, [textures, colorPreset, sceneTheme, shading, node.material, node.materialPreset])

  const yAxis = useMemo(() => new THREE.Vector3(0, 1, 0), [])
  const composedQuat = useMemo(() => {
    const yawQuat = new THREE.Quaternion().setFromAxisAngle(yAxis, node.rotation ?? 0)
    return new THREE.Quaternion().copy(surfaceQuat).multiply(yawQuat)
  }, [surfaceQuat, node.rotation, yAxis])

  if (!segment) return null

  const segPos = segment.position ?? [0, 0, 0]
  const segRotY = segment.rotation ?? 0

  return (
    <group position={segPos} rotation-y={segRotY}>
      <group
        position={[node.position[0] ?? 0, node.position[1] ?? 0, node.position[2] ?? 0]}
        quaternion={composedQuat}
        ref={ref}
        visible={node.visible}
      >
        <mesh
          castShadow
          geometry={geometry}
          material={material}
          name="cupola-surface"
          receiveShadow
          {...handlers}
        />
      </group>
    </group>
  )
}

export default CupolaRenderer
