import { useMemo } from 'react';
import * as THREE from 'three';
import type { Point, FloorType } from '@house-plan-maker/shared';
import { getFloorPbr } from './utils/pbrTextures';

interface FloorCeilingProps {
  readonly shape: readonly Point[];
  readonly wallHeight: number;
  readonly floorType?: FloorType;
  /**
   * Distance (meters) a stretch ceiling hangs below the structural ceiling.
   * When > 0 a semi-transparent tinted plane is drawn at
   * `wallHeight - stretchCeilingOffset` plus a faint outline, so the user
   * can see how much vertical space is lost to the suspended ceiling.
   */
  readonly stretchCeilingOffset?: number;
}

/**
 * Build a ShapeGeometry from the room polygon and rescale its UVs so the
 * floor's PBR texture tiles every `tileMeters` instead of stretching once
 * over the full room. ShapeGeometry's default UVs are the shape coordinates
 * directly (in meters), so dividing by `tileMeters` yields a UV that
 * advances by 1.0 every `tileMeters` along each axis — exactly what
 * `wrapS/wrapT = RepeatWrapping` needs to tile the texture.
 *
 * This puts the tiling on the geometry side, which means a single shared
 * material instance can be reused across rooms of different sizes without
 * mutating the material's texture.repeat (which would affect every mesh
 * sharing the material).
 */
function buildFloorGeometry(shape: readonly Point[], tileMeters: number): THREE.ShapeGeometry | null {
  if (shape.length < 3) return null;

  const threeShape = new THREE.Shape();
  threeShape.moveTo(shape[0].x, shape[0].y);
  for (let i = 1; i < shape.length; i++) {
    threeShape.lineTo(shape[i].x, shape[i].y);
  }
  threeShape.closePath();

  const geometry = new THREE.ShapeGeometry(threeShape);

  // Rescale UVs in place. Default ShapeGeometry UVs equal (x, y) coordinates
  // in meters; dividing by tileMeters gives the desired tile density.
  const uv = geometry.attributes.uv;
  if (uv) {
    const scale = 1 / tileMeters;
    for (let i = 0; i < uv.count; i++) {
      uv.setXY(i, uv.getX(i) * scale, uv.getY(i) * scale);
    }
    uv.needsUpdate = true;
  }

  return geometry;
}

/**
 * Build a flat ShapeGeometry for the room polygon, without any UV rescaling.
 * Used for the stretch-ceiling overlay where the shape is tinted solid and
 * UV tiling is irrelevant.
 */
function buildFlatShapeGeometry(shape: readonly Point[]): THREE.ShapeGeometry | null {
  if (shape.length < 3) return null;
  const threeShape = new THREE.Shape();
  threeShape.moveTo(shape[0].x, shape[0].y);
  for (let i = 1; i < shape.length; i++) {
    threeShape.lineTo(shape[i].x, shape[i].y);
  }
  threeShape.closePath();
  return new THREE.ShapeGeometry(threeShape);
}

/**
 * Outline edge-pair list closing the room polygon, suitable for
 * THREE.LineSegments. The floor mesh uses
 * `rotation=[-π/2, 0, 0], scale=[1, -1, 1]`, which maps a local
 * `(x_shape, y_shape)` vertex onto world `(x_shape, 0, y_shape)`. We
 * replicate that mapping here so the outline sits exactly above the floor
 * polygon instead of being mirrored onto the opposite side.
 */
function buildOutlineEdgePairs(shape: readonly Point[], y: number): Float32Array {
  const n = shape.length;
  const points = new Float32Array(n * 2 * 3);
  for (let i = 0; i < n; i++) {
    const a = shape[i];
    const b = shape[(i + 1) % n];
    const base = i * 6;
    points[base + 0] = a.x;
    points[base + 1] = y;
    points[base + 2] = a.y;
    points[base + 3] = b.x;
    points[base + 4] = y;
    points[base + 5] = b.y;
  }
  return points;
}

export function FloorCeiling({
  shape,
  wallHeight,
  floorType = 'CONCRETE',
  stretchCeilingOffset = 0,
}: FloorCeilingProps) {
  const pbr = useMemo(() => getFloorPbr(floorType), [floorType]);
  const geometry = useMemo(
    () => buildFloorGeometry(shape, pbr.tileMeters),
    [shape, pbr.tileMeters],
  );

  // Stretch ceiling plane geometry (only built when the feature is active).
  const stretchGeometry = useMemo(
    () => (stretchCeilingOffset > 0 ? buildFlatShapeGeometry(shape) : null),
    [shape, stretchCeilingOffset],
  );

  const stretchY = Math.max(0, wallHeight - stretchCeilingOffset);

  // Edge outline buffer for the stretch ceiling (closed polyline).
  const outlineGeometry = useMemo(() => {
    if (stretchCeilingOffset <= 0 || shape.length < 3) return null;
    const geom = new THREE.BufferGeometry();
    geom.setAttribute(
      'position',
      new THREE.BufferAttribute(buildOutlineEdgePairs(shape, stretchY), 3),
    );
    return geom;
  }, [shape, stretchCeilingOffset, stretchY]);

  if (!geometry) return null;

  return (
    <>
      <mesh
        geometry={geometry}
        material={pbr.material}
        rotation={[-Math.PI / 2, 0, 0]}
        scale={[1, -1, 1]}
        position={[0, 0, 0]}
        receiveShadow
      />
      {/* Stretch ceiling — a soft blue tinted plane facing *downward* so it
          is only visible from inside the room, plus a thin edge outline at
          the same height. The plane uses depthWrite=false so items above
          (e.g. pipes) still render correctly through it. */}
      {stretchGeometry && (
        // Same rotation+scale as the floor so the shape maps onto the same
        // world (x, -y) plane, but raised to the stretch ceiling height.
        // DoubleSide + depthWrite=false keeps it visible from both sides and
        // avoids z-fighting with objects beneath it.
        <mesh
          geometry={stretchGeometry}
          rotation={[-Math.PI / 2, 0, 0]}
          scale={[1, -1, 1]}
          position={[0, stretchY, 0]}
        >
          <meshBasicMaterial
            color="#60a5fa"
            transparent
            opacity={0.22}
            side={THREE.DoubleSide}
            depthWrite={false}
          />
        </mesh>
      )}
      {outlineGeometry && (
        <lineSegments>
          <bufferGeometry attach="geometry" {...outlineGeometry} />
          <lineBasicMaterial attach="material" color="#2563eb" linewidth={1} transparent opacity={0.85} />
        </lineSegments>
      )}
    </>
  );
}
