import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { Stage, Layer, Line, Text, Group } from 'react-konva';
import type Konva from 'konva';
import type { Room, Point } from '@house-plan-maker/shared';
import { getApartment, getRooms, updateRoom } from '../api/client';
import { useEditorZoom } from '../components/editor/hooks/useEditorZoom';
import { DEFAULT_ZOOM } from '../components/editor/types';
import { Button } from '../components/ui/Button';
import { LoadingSpinner } from '../components/ui/LoadingSpinner';
import { ErrorBanner } from '../components/ui/ErrorBanner';
import styles from './apartment-floor-plan-page.module.css';

/** Room fill color. */
const ROOM_FILL = '#f8f9fa';
/** Room stroke color. */
const ROOM_STROKE = '#adb5bd';
/** Selected room stroke color. */
const ROOM_SELECTED_STROKE = '#4c6ef5';
/** Room name label color. */
const LABEL_COLOR = '#495057';
/** Dimension label color. */
const DIM_COLOR = '#868e96';

/** Compute the area of a polygon using the shoelace formula. */
function computePolygonArea(shape: readonly Point[]): number {
  if (shape.length < 3) return 0;
  let area = 0;
  for (let i = 0; i < shape.length; i++) {
    const j = (i + 1) % shape.length;
    area += shape[i].x * shape[j].y;
    area -= shape[j].x * shape[i].y;
  }
  return Math.abs(area) / 2;
}

/** Compute bounding box of a shape. */
function shapeBounds(shape: readonly Point[]): {
  minX: number;
  minY: number;
  maxX: number;
  maxY: number;
  width: number;
  height: number;
} {
  if (shape.length === 0) {
    return { minX: 0, minY: 0, maxX: 0, maxY: 0, width: 0, height: 0 };
  }
  const xs = shape.map((p) => p.x);
  const ys = shape.map((p) => p.y);
  const minX = Math.min(...xs);
  const minY = Math.min(...ys);
  const maxX = Math.max(...xs);
  const maxY = Math.max(...ys);
  return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
}

/** Compute centroid of a polygon. */
function shapeCentroid(shape: readonly Point[]): Point {
  if (shape.length === 0) return { x: 0, y: 0 };
  const bounds = shapeBounds(shape);
  return {
    x: (bounds.minX + bounds.maxX) / 2,
    y: (bounds.minY + bounds.maxY) / 2,
  };
}

export function ApartmentFloorPlanPage() {
  const { t } = useTranslation();
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();

  const [apartmentName, setApartmentName] = useState('');
  const [rooms, setRooms] = useState<readonly Room[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const [selectedRoomId, setSelectedRoomId] = useState<string | null>(null);

  const [zoom, setZoom] = useState(DEFAULT_ZOOM);
  const [panOffset, setPanOffset] = useState<Point>({ x: 50, y: 50 });

  const [stageSize, setStageSize] = useState({ width: 800, height: 600 });
  const containerRef = useRef<HTMLDivElement>(null);
  const stageRef = useRef<Konva.Stage | null>(null);

  // Track which room is being dragged to avoid selecting on drag end
  const isDraggingRef = useRef(false);

  const fetchData = useCallback(async () => {
    if (!id) return;
    try {
      setError(null);
      const [apt, roomList] = await Promise.all([
        getApartment(id),
        getRooms(id),
      ]);
      setApartmentName(apt.name);
      setRooms(roomList);
    } catch (err: unknown) {
      const message =
        err instanceof Error ? err.message : t('rooms.error.load');
      setError(message);
    } finally {
      setLoading(false);
    }
  }, [id]);

  useEffect(() => {
    fetchData();
  }, [fetchData]);

  // Resize observer for the canvas container
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        setStageSize({ width: Math.floor(width), height: Math.floor(height) });
      }
    });

    observer.observe(container);
    return () => observer.disconnect();
  }, []);

  const {
    handleWheel,
    zoomIn,
    zoomOut,
    resetView,
    isPanningRef,
    handlePanStart,
    handlePanMove,
    handlePanEnd,
  } = useEditorZoom({
    zoom,
    panOffset,
    onZoomChange: setZoom,
    onPanChange: setPanOffset,
  });

  const handleStageMouseDown = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      handlePanStart(e);
      // Left-click on empty stage => deselect
      if (e.evt.button === 0 && e.target === e.target.getStage()) {
        setSelectedRoomId(null);
      }
    },
    [handlePanStart],
  );

  const handleStageMouseMove = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      handlePanMove(e);
    },
    [handlePanMove],
  );

  const handleStageMouseUp = useCallback(() => {
    handlePanEnd();
  }, [handlePanEnd]);

  const handleRoomClick = useCallback(
    (roomId: string) => {
      // Don't select if we just finished dragging
      if (isDraggingRef.current) {
        isDraggingRef.current = false;
        return;
      }
      setSelectedRoomId(roomId);
    },
    [],
  );

  const handleRoomDblClick = useCallback(
    (roomId: string) => {
      if (!id) return;
      navigate(`/apartments/${id}/rooms/${roomId}/editor`);
    },
    [id, navigate],
  );

  const handleRoomDragEnd = useCallback(
    async (roomId: string, newPosX: number, newPosY: number) => {
      isDraggingRef.current = true;
      try {
        const updated = await updateRoom(roomId, {
          posX: newPosX,
          posY: newPosY,
        });
        setRooms((prev) =>
          prev.map((r) => (r.id === roomId ? updated : r)),
        );
      } catch (err: unknown) {
        const message =
          err instanceof Error ? err.message : t('rooms.error.operation');
        setError(message);
        // Refetch to restore correct positions
        await fetchData();
      }
    },
    [fetchData],
  );

  const selectedRoom = useMemo(
    () => rooms.find((r) => r.id === selectedRoomId) ?? null,
    [rooms, selectedRoomId],
  );

  const selectedRoomArea = useMemo(() => {
    if (!selectedRoom || selectedRoom.shape.length < 3) return null;
    return computePolygonArea(selectedRoom.shape);
  }, [selectedRoom]);

  const selectedRoomBounds = useMemo(() => {
    if (!selectedRoom || selectedRoom.shape.length < 3) return null;
    return shapeBounds(selectedRoom.shape);
  }, [selectedRoom]);

  const zoomPercent = Math.round((zoom / DEFAULT_ZOOM) * 100);

  const handleBackClick = useCallback(() => {
    if (!id) return;
    navigate(`/apartments/${id}`);
  }, [id, navigate]);

  if (loading) {
    return <LoadingSpinner />;
  }

  if (error && rooms.length === 0) {
    return <ErrorBanner message={error} />;
  }

  return (
    <div className={styles.container}>
      <div className={styles.toolbar}>
        <div className={styles.toolbarLeft}>
          <Button variant="ghost" size="sm" onClick={handleBackClick}>
            &larr; {t('floorPlan.back')}
          </Button>
          <span className={styles.title}>
            {apartmentName} — {t('breadcrumb.floorPlan')}
          </span>
        </div>
        <div className={styles.toolbarRight}>
          <Button variant="ghost" size="sm" onClick={zoomOut}>
            -
          </Button>
          <span className={styles.zoomDisplay}>{zoomPercent}%</span>
          <Button variant="ghost" size="sm" onClick={zoomIn}>
            +
          </Button>
          <Button variant="secondary" size="sm" onClick={resetView}>
            {t('floorPlan.reset')}
          </Button>
        </div>
      </div>

      {error && <ErrorBanner message={error} onDismiss={() => setError(null)} />}

      <div className={styles.canvasWrapper} ref={containerRef}>
        <Stage
          ref={stageRef}
          width={stageSize.width}
          height={stageSize.height}
          onWheel={handleWheel}
          onMouseDown={handleStageMouseDown}
          onMouseMove={handleStageMouseMove}
          onMouseUp={handleStageMouseUp}
          style={{ cursor: isPanningRef.current ? 'grabbing' : 'default', background: '#ffffff' }}
        >
          <Layer>
            {rooms.map((room) => (
              <FloorPlanRoom
                key={room.id}
                room={room}
                zoom={zoom}
                panOffset={panOffset}
                isSelected={room.id === selectedRoomId}
                onClick={handleRoomClick}
                onDblClick={handleRoomDblClick}
                onDragEnd={handleRoomDragEnd}
              />
            ))}
          </Layer>
        </Stage>

        {selectedRoom && (
          <div className={styles.roomInfo}>
            <div className={styles.roomInfoTitle}>{selectedRoom.name}</div>
            {selectedRoomBounds && (
              <div className={styles.roomInfoRow}>
                <span>
                  {selectedRoomBounds.width.toFixed(2)} x{' '}
                  {selectedRoomBounds.height.toFixed(2)} m
                </span>
              </div>
            )}
            {selectedRoomArea != null && (
              <div className={styles.roomInfoRow}>
                <span>{t('apartmentCard.area')} {selectedRoomArea.toFixed(2)} m&sup2;</span>
              </div>
            )}
            <div className={styles.roomInfoRow}>
              <span style={{ fontSize: '0.75rem', color: 'var(--color-text-secondary)' }}>
                {t('floorPlan.dblClickToEdit')}
              </span>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

// ── Individual room rendering on the floor plan ──

interface FloorPlanRoomProps {
  readonly room: Room;
  readonly zoom: number;
  readonly panOffset: Point;
  readonly isSelected: boolean;
  readonly onClick: (roomId: string) => void;
  readonly onDblClick: (roomId: string) => void;
  readonly onDragEnd: (roomId: string, posX: number, posY: number) => void;
}

function FloorPlanRoom({
  room,
  zoom,
  panOffset,
  isSelected,
  onClick,
  onDblClick,
  onDragEnd,
}: FloorPlanRoomProps) {
  const shape = room.shape;

  // If there is no shape, render a placeholder rectangle based on width/height
  const effectiveShape: readonly Point[] = useMemo(() => {
    if (shape.length >= 3) return shape;
    const w = room.width ?? 3;
    const h = room.height ?? 3;
    return [
      { x: 0, y: 0 },
      { x: w, y: 0 },
      { x: w, y: h },
      { x: 0, y: h },
    ];
  }, [shape, room.width, room.height]);

  // Screen coordinates for the polygon, relative to the group origin
  const screenPoints = useMemo(() => {
    return effectiveShape.flatMap((p) => [p.x * zoom, p.y * zoom]);
  }, [effectiveShape, zoom]);

  // Centroid for label placement (in local/zoom coords)
  const centroid = useMemo(() => {
    const c = shapeCentroid(effectiveShape);
    return { x: c.x * zoom, y: c.y * zoom };
  }, [effectiveShape, zoom]);

  // Bounding box for dimension labels
  const bounds = useMemo(() => shapeBounds(effectiveShape), [effectiveShape]);

  // Group position (room.posX/posY in world coords -> screen coords)
  const groupX = room.posX * zoom + panOffset.x;
  const groupY = room.posY * zoom + panOffset.y;

  const handleDragEnd = useCallback(
    (e: Konva.KonvaEventObject<DragEvent>) => {
      const node = e.target;
      const newScreenX = node.x();
      const newScreenY = node.y();
      // Convert back to world coords
      const newPosX = (newScreenX - panOffset.x) / zoom;
      const newPosY = (newScreenY - panOffset.y) / zoom;
      onDragEnd(room.id, newPosX, newPosY);
    },
    [room.id, zoom, panOffset, onDragEnd],
  );

  const handleClick = useCallback(() => {
    onClick(room.id);
  }, [room.id, onClick]);

  const handleDblClick = useCallback(() => {
    onDblClick(room.id);
  }, [room.id, onDblClick]);

  const dimLabel = `${bounds.width.toFixed(1)} x ${bounds.height.toFixed(1)} m`;

  return (
    <Group
      x={groupX}
      y={groupY}
      draggable
      onDragEnd={handleDragEnd}
      onClick={handleClick}
      onDblClick={handleDblClick}
    >
      {/* Room polygon */}
      <Line
        points={screenPoints}
        closed
        fill={ROOM_FILL}
        stroke={isSelected ? ROOM_SELECTED_STROKE : ROOM_STROKE}
        strokeWidth={isSelected ? 3 : 1.5}
      />

      {/* Room name label */}
      <Text
        x={centroid.x}
        y={centroid.y - 12}
        text={room.name}
        fontSize={14}
        fontStyle="bold"
        fill={LABEL_COLOR}
        align="center"
        offsetX={room.name.length * 3.5}
        listening={false}
      />

      {/* Dimension label */}
      <Text
        x={centroid.x}
        y={centroid.y + 6}
        text={dimLabel}
        fontSize={11}
        fill={DIM_COLOR}
        align="center"
        offsetX={dimLabel.length * 2.8}
        listening={false}
      />
    </Group>
  );
}
