import { useMemo } from 'react';
import * as THREE from 'three';
import type { Wall, WallOpening } from '@house-plan-maker/shared';
import {
  wallRotationY,
  positionAlongWall3D,
} from './utils/wallGeometry';

interface WindowOpeningProps {
  readonly opening: WallOpening;
  readonly wall: Wall;
  readonly isSelected: boolean;
  readonly onSelect?: (id: string) => void;
}

const FRAME_COLOR = '#c0c0c0';
const GLASS_COLOR = '#a8d8ea';
/** Fallback frame thickness for legacy openings without an explicit value. */
const DEFAULT_FRAME_THICKNESS = 0.03;

export function WindowOpening({ opening, wall, isSelected, onSelect }: WindowOpeningProps) {
  const rotY = useMemo(() => wallRotationY(wall), [wall]);

  const [cx, cz] = useMemo(
    () => positionAlongWall3D(wall, opening.positionAlongWall),
    [wall, opening.positionAlongWall],
  );

  const frameColor = isSelected ? '#6fa8dc' : FRAME_COLOR;
  const halfWidth = opening.width / 2;
  const halfThick = wall.thickness / 2 + 0.005;
  // Per-opening frame thickness with a sane fallback for old data. The
  // mullion thickness is derived from the frame thickness so the grid
  // dividers stay visually proportional regardless of the user's choice.
  const frameThickness = Math.max(0, opening.frameThickness ?? DEFAULT_FRAME_THICKNESS);

  // Window reveal (откос). The slope panels project OUTWARD from the
  // window frame's outer face, away from the room interior, lining the wall
  // opening as the wall extends toward the exterior. `0` keeps the window
  // flush. The schema cap of 2 m is plenty so we don't clamp again here.
  const slopeDepth = Math.max(0, opening.slopeDepth ?? 0);
  const slopeColor = isSelected ? '#9bbfdc' : '#e8e0d4';
  // Visible jamb thickness — substantial enough to read as an architectural
  // element rather than a hairline.
  const slopeStripThickness = 0.025;
  // Centre of the slope slab in window-local z. The wall's room-facing side
  // sits at -halfThick in window-local coords (verified empirically), so the
  // panels grow outward into the wall by spanning -halfThick to
  // -halfThick - slopeDepth.
  const slopeFaceZ = -halfThick - slopeDepth / 2;

  // Grid subdivision — clamp to sensible bounds. `cols` verticals produce
  // `cols-1` internal mullions; same for rows. Default to a 2×2 grid
  // (single vertical + single horizontal mullion) to match the classic
  // look for windows without an explicit grid set.
  const gridCols = Math.max(1, Math.min(10, Math.round(opening.gridCols ?? 2)));
  const gridRows = Math.max(1, Math.min(10, Math.round(opening.gridRows ?? 2)));
  const mullionThickness = frameThickness * 0.55;

  return (
    <group
      position={[cx, opening.elevationFromFloor + opening.height / 2, cz]}
      rotation={[0, rotY, 0]}
      onClick={onSelect ? (e) => { e.stopPropagation(); onSelect(opening.id); } : undefined}
    >
      {/* Reveal (откос) panels — flat jamb/sill strips lining the wall
          opening between the room face and the window frame. Rendered only
          when the user has set a positive slopeDepth. Each panel is a thin
          slab oriented parallel to the wall face, just inside the opening
          edge so the wall material isn't visible against the frame. */}
      {slopeDepth > 0 && (
        <>
          {/* Reveal panels are visual only — `raycast={() => null}` opts them out
              of pointer hit-testing so the window's clickable area stays
              limited to the frame and glass instead of ballooning to the
              entire reveal sleeve. */}
          {/* Left jamb */}
          <mesh position={[-halfWidth - slopeStripThickness / 2, 0, slopeFaceZ]} raycast={() => null}>
            <boxGeometry args={[slopeStripThickness, opening.height, slopeDepth]} />
            <meshStandardMaterial color={slopeColor} roughness={0.7} />
          </mesh>
          {/* Right jamb */}
          <mesh position={[halfWidth + slopeStripThickness / 2, 0, slopeFaceZ]} raycast={() => null}>
            <boxGeometry args={[slopeStripThickness, opening.height, slopeDepth]} />
            <meshStandardMaterial color={slopeColor} roughness={0.7} />
          </mesh>
          {/* Head (top) */}
          <mesh position={[0, opening.height / 2 + slopeStripThickness / 2, slopeFaceZ]} raycast={() => null}>
            <boxGeometry args={[opening.width + slopeStripThickness * 2, slopeStripThickness, slopeDepth]} />
            <meshStandardMaterial color={slopeColor} roughness={0.7} />
          </mesh>
          {/* Sill (bottom) */}
          <mesh position={[0, -opening.height / 2 - slopeStripThickness / 2, slopeFaceZ]} raycast={() => null}>
            <boxGeometry args={[opening.width + slopeStripThickness * 2, slopeStripThickness, slopeDepth]} />
            <meshStandardMaterial color={slopeColor} roughness={0.7} />
          </mesh>
        </>
      )}
      {/* Window frame, glass and mullions are all wrapped in a sub-group
          shifted by -slopeDepth so they sit at the outer end of the reveal
          panels. With slopeDepth = 0 the offset is zero and behaviour matches
          the legacy flush-mounted window. */}
      {/* Window frame — four sides */}
      {/* Left */}
      <mesh position={[-halfWidth, 0, -slopeDepth]} castShadow>
        <boxGeometry args={[frameThickness, opening.height, halfThick * 2]} />
        <meshStandardMaterial color={frameColor} roughness={0.4} />
      </mesh>
      {/* Right */}
      <mesh position={[halfWidth, 0, -slopeDepth]} castShadow>
        <boxGeometry args={[frameThickness, opening.height, halfThick * 2]} />
        <meshStandardMaterial color={frameColor} roughness={0.4} />
      </mesh>
      {/* Top */}
      <mesh position={[0, opening.height / 2, -slopeDepth]} castShadow>
        <boxGeometry args={[opening.width + frameThickness, frameThickness, halfThick * 2]} />
        <meshStandardMaterial color={frameColor} roughness={0.4} />
      </mesh>
      {/* Bottom (sill) */}
      <mesh position={[0, -opening.height / 2, -slopeDepth]} castShadow>
        <boxGeometry args={[opening.width + frameThickness, frameThickness, halfThick * 2]} />
        <meshStandardMaterial color={frameColor} roughness={0.4} />
      </mesh>

      {/* Glass pane */}
      <mesh position={[0, 0, -slopeDepth]}>
        <planeGeometry args={[opening.width, opening.height]} />
        <meshStandardMaterial
          color={GLASS_COLOR}
          transparent
          opacity={0.3}
          side={THREE.DoubleSide}
          roughness={0.1}
          metalness={0.2}
        />
      </mesh>

      {/* Vertical mullions — `gridCols - 1` internal dividers spaced
          evenly between the left and right frame posts. */}
      {Array.from({ length: gridCols - 1 }).map((_, i) => {
        const x = -halfWidth + ((i + 1) * opening.width) / gridCols;
        return (
          <mesh key={`vmul-${i}`} position={[x, 0, -slopeDepth]} castShadow>
            <boxGeometry args={[mullionThickness, opening.height, mullionThickness]} />
            <meshStandardMaterial color={frameColor} roughness={0.4} />
          </mesh>
        );
      })}

      {/* Horizontal mullions — `gridRows - 1` internal dividers spaced
          evenly between the top and bottom frame rails. */}
      {Array.from({ length: gridRows - 1 }).map((_, i) => {
        const y = -opening.height / 2 + ((i + 1) * opening.height) / gridRows;
        return (
          <mesh key={`hmul-${i}`} position={[0, y, -slopeDepth]} castShadow>
            <boxGeometry args={[opening.width, mullionThickness, mullionThickness]} />
            <meshStandardMaterial color={frameColor} roughness={0.4} />
          </mesh>
        );
      })}
    </group>
  );
}
