/**
 * KonvaStage — core Konva rendering layer.
 *
 * Renders:
 * 1. Artboard background (Rect/Circle/Path based on ArtboardConfig)
 * 2. All canvas objects mapped to Konva primitives
 * 3. Transformer for selection handles
 * 4. Rubber-band selection rectangle for multi-select
 */

import {
  useRef,
  useEffect,
  useState,
  useCallback,
  type RefObject,
} from 'react';
import {
  Stage,
  Layer,
  Rect,
  Circle,
  Ellipse,
  Line,
  Image as KonvaImage,
  Text as KonvaText,
  Transformer,
  Path,
} from 'react-konva';
import type Konva from 'konva';
import type {
  ArtboardConfig,
  CanvasObject,
  CanvasObjectType,
} from '../../types/canvas';
import { toPx, artboardClipPath } from '../../utils/artboardShapes';

// -- Types --------------------------------------------------------------------

export type CanvasTool = 'pointer' | 'rect' | 'circle' | 'ellipse' | 'line' | 'text';

export interface KonvaStageProps {
  width: number;
  height: number;
  artboard: ArtboardConfig | null;
  objects: CanvasObject[];
  selectedIds: string[];
  activeTool: CanvasTool;
  onSelect: (ids: string[], additive: boolean) => void;
  onDeselectAll: () => void;
  onAddObject: (obj: CanvasObject) => void;
  onUpdateObject: (id: string, changes: Partial<CanvasObject>) => void;
  stageRef: RefObject<Konva.Stage | null>;
}

// -- Helpers ------------------------------------------------------------------

let _nextId = 1;
function nextId(type: CanvasObjectType): string {
  return `${type}-${Date.now()}-${_nextId++}`;
}

const DASH_MAP: Record<string, number[]> = {
  solid: [],
  dashed: [10, 5],
  dotted: [2, 4],
};

function getLineDash(lineStyle: string): number[] {
  return DASH_MAP[lineStyle] ?? [];
}

// -- Component ----------------------------------------------------------------

export default function KonvaStage({
  width,
  height,
  artboard,
  objects,
  selectedIds,
  activeTool,
  onSelect,
  onDeselectAll,
  onAddObject,
  onUpdateObject,
  stageRef,
}: KonvaStageProps) {
  const transformerRef = useRef<Konva.Transformer | null>(null);
  const layerRef = useRef<Konva.Layer | null>(null);

  // Rubber-band selection state
  const [rubberBand, setRubberBand] = useState<{
    x: number;
    y: number;
    width: number;
    height: number;
    visible: boolean;
  }>({ x: 0, y: 0, width: 0, height: 0, visible: false });
  const rubberStart = useRef<{ x: number; y: number } | null>(null);

  // Artboard pixel dimensions
  const artW = artboard ? toPx(artboard.width, artboard.unit) : width;
  const artH = artboard ? toPx(artboard.height, artboard.unit) : height;

  // Center artboard on stage
  const offsetX = Math.max(0, (width - artW) / 2);
  const offsetY = Math.max(0, (height - artH) / 2);

  // -- Sync transformer with selection ------------------------------------

  useEffect(() => {
    const tr = transformerRef.current;
    const stage = stageRef.current;
    if (!tr || !stage) return;

    const selectedNodes = selectedIds
      .map((id) => stage.findOne(`#${id}`))
      .filter((n): n is Konva.Node => n != null);

    tr.nodes(selectedNodes);
    tr.getLayer()?.batchDraw();
  }, [selectedIds, stageRef]);

  // -- Artboard background ------------------------------------------------

  function renderArtboard() {
    if (!artboard) return null;

    const clipPathData = artboardClipPath(artboard);

    // Common artboard background rect (for all shapes, acts as bounding box)
    const bgRect = (
      <Rect
        key="artboard-bg"
        x={offsetX}
        y={offsetY}
        width={artW}
        height={artH}
        fill="#ffffff"
        stroke="#cccccc"
        strokeWidth={1}
        listening={false}
      />
    );

    if (clipPathData) {
      // Render the clip path outline for shield/pennant/custom shapes
      return (
        <>
          {bgRect}
          <Path
            key="artboard-clip"
            x={offsetX}
            y={offsetY}
            data={clipPathData}
            stroke="#999999"
            strokeWidth={1}
            fill="rgba(255,255,255,0.5)"
            listening={false}
          />
        </>
      );
    }

    if (artboard.shape === 'circle') {
      const r = Math.min(artW, artH) / 2;
      return (
        <>
          {bgRect}
          <Circle
            key="artboard-circle"
            x={offsetX + artW / 2}
            y={offsetY + artH / 2}
            radius={r}
            stroke="#999999"
            strokeWidth={1}
            fill="rgba(255,255,255,0.5)"
            listening={false}
          />
        </>
      );
    }

    if (artboard.shape === 'oval') {
      return (
        <>
          {bgRect}
          <Ellipse
            key="artboard-oval"
            x={offsetX + artW / 2}
            y={offsetY + artH / 2}
            radiusX={artW / 2}
            radiusY={artH / 2}
            stroke="#999999"
            strokeWidth={1}
            fill="rgba(255,255,255,0.5)"
            listening={false}
          />
        </>
      );
    }

    // Default: rect/square — just the bg rect is enough
    return bgRect;
  }

  // -- Render canvas objects ------------------------------------------------

  function renderObject(obj: CanvasObject) {
    if (!obj.visible) return null;

    const commonProps = {
      id: obj.id,
      x: obj.x + offsetX,
      y: obj.y + offsetY,
      rotation: obj.rotation,
      opacity: obj.opacity,
      draggable: !obj.locked && activeTool === 'pointer',
      onClick: (e: Konva.KonvaEventObject<MouseEvent>) => {
        e.cancelBubble = true;
        const isShift = e.evt.shiftKey;
        onSelect([obj.id], isShift);
      },
      onDragEnd: (e: Konva.KonvaEventObject<DragEvent>) => {
        onUpdateObject(obj.id, {
          x: e.target.x() - offsetX,
          y: e.target.y() - offsetY,
        });
      },
      onTransformEnd: (e: Konva.KonvaEventObject<Event>) => {
        const node = e.target;
        const scaleX = node.scaleX();
        const scaleY = node.scaleY();
        node.scaleX(1);
        node.scaleY(1);

        const changes: Partial<CanvasObject> = {
          x: node.x() - offsetX,
          y: node.y() - offsetY,
          rotation: node.rotation(),
        };

        if (obj.type === 'rect' || obj.type === 'image') {
          (changes as Record<string, unknown>).width = Math.max(5, node.width() * scaleX);
          (changes as Record<string, unknown>).height = Math.max(5, node.height() * scaleY);
        } else if (obj.type === 'circle') {
          (changes as Record<string, unknown>).radius = Math.max(5, (node.width() * scaleX) / 2);
        } else if (obj.type === 'ellipse') {
          (changes as Record<string, unknown>).radiusX = Math.max(5, (node.width() * scaleX) / 2);
          (changes as Record<string, unknown>).radiusY = Math.max(5, (node.height() * scaleY) / 2);
        } else if (obj.type === 'text') {
          // Scale width for wrapping, keep fontSize unchanged
          (changes as Record<string, unknown>).width = Math.max(20, node.width() * scaleX);
        }

        onUpdateObject(obj.id, changes);
      },
    };

    switch (obj.type) {
      case 'rect':
        return (
          <Rect
            key={obj.id}
            {...commonProps}
            width={obj.width}
            height={obj.height}
            fill={obj.fill}
            stroke={obj.stroke}
            strokeWidth={obj.strokeWidth}
          />
        );

      case 'circle':
        return (
          <Circle
            key={obj.id}
            {...commonProps}
            radius={obj.radius}
            fill={obj.fill}
            stroke={obj.stroke}
            strokeWidth={obj.strokeWidth}
          />
        );

      case 'ellipse':
        return (
          <Ellipse
            key={obj.id}
            {...commonProps}
            radiusX={obj.radiusX}
            radiusY={obj.radiusY}
            fill={obj.fill}
            stroke={obj.stroke}
            strokeWidth={obj.strokeWidth}
          />
        );

      case 'line':
        return (
          <Line
            key={obj.id}
            {...commonProps}
            points={obj.points}
            stroke={obj.stroke}
            strokeWidth={obj.strokeWidth}
            dash={getLineDash(obj.lineStyle)}
          />
        );

      case 'image':
        return (
          <KonvaImageWrapper
            key={obj.id}
            {...commonProps}
            width={obj.width}
            height={obj.height}
            src={obj.src}
          />
        );

      case 'text':
        return (
          <KonvaText
            key={obj.id}
            {...commonProps}
            text={obj.text}
            fontFamily={obj.fontFamily}
            fontSize={obj.fontSize}
            fill={obj.fill}
            stroke={obj.stroke}
            strokeWidth={obj.strokeWidth}
            width={obj.width}
            letterSpacing={obj.letterSpacing}
            lineHeight={obj.lineHeight}
          />
        );

      default:
        return null;
    }
  }

  // -- Shape creation on click in tool mode --------------------------------

  const handleStageMouseDown = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      // If clicking on empty stage area
      const clickedOnEmpty = e.target === e.target.getStage();

      if (activeTool === 'pointer') {
        if (clickedOnEmpty) {
          onDeselectAll();
          // Start rubber-band selection
          const pos = e.target.getStage()?.getPointerPosition();
          if (pos) {
            rubberStart.current = { x: pos.x, y: pos.y };
            setRubberBand({ x: pos.x, y: pos.y, width: 0, height: 0, visible: true });
          }
        }
        return;
      }

      // Tool mode: create a shape at click position
      if (!clickedOnEmpty) return;

      const pos = e.target.getStage()?.getPointerPosition();
      if (!pos) return;

      const x = pos.x - offsetX;
      const y = pos.y - offsetY;

      let newObj: CanvasObject | null = null;

      switch (activeTool) {
        case 'rect':
          newObj = {
            id: nextId('rect'),
            type: 'rect',
            name: 'Rectangle',
            x,
            y,
            width: 100,
            height: 80,
            rotation: 0,
            visible: true,
            locked: false,
            opacity: 1,
            fill: 'transparent',
            stroke: '#000000',
            strokeWidth: 2,
          };
          break;

        case 'circle':
          newObj = {
            id: nextId('circle'),
            type: 'circle',
            name: 'Circle',
            x,
            y,
            radius: 50,
            rotation: 0,
            visible: true,
            locked: false,
            opacity: 1,
            fill: 'transparent',
            stroke: '#000000',
            strokeWidth: 2,
          };
          break;

        case 'ellipse':
          newObj = {
            id: nextId('ellipse'),
            type: 'ellipse',
            name: 'Ellipse',
            x,
            y,
            radiusX: 60,
            radiusY: 40,
            rotation: 0,
            visible: true,
            locked: false,
            opacity: 1,
            fill: 'transparent',
            stroke: '#000000',
            strokeWidth: 2,
          };
          break;

        case 'line':
          newObj = {
            id: nextId('line'),
            type: 'line',
            name: 'Line',
            x,
            y,
            points: [0, 0, 100, 0],
            rotation: 0,
            visible: true,
            locked: false,
            opacity: 1,
            stroke: '#000000',
            strokeWidth: 2,
            lineStyle: 'solid',
            dash: [],
          };
          break;

        case 'text':
          newObj = {
            id: nextId('text'),
            type: 'text',
            name: 'Text',
            x,
            y,
            rotation: 0,
            visible: true,
            locked: false,
            opacity: 1,
            text: 'Text',
            fontFamily: 'Roboto',
            fontSize: 24,
            letterSpacing: 0,
            lineHeight: 1.2,
            fill: '#000000',
            stroke: 'transparent',
            strokeWidth: 0,
            width: 200,
          };
          break;
      }

      if (newObj) {
        onAddObject(newObj);
        onSelect([newObj.id], false);
      }
    },
    [activeTool, offsetX, offsetY, onDeselectAll, onAddObject, onSelect],
  );

  // -- Rubber-band selection mouse move / up --------------------------------

  const handleStageMouseMove = useCallback(
    (e: Konva.KonvaEventObject<MouseEvent>) => {
      if (!rubberStart.current) return;
      const pos = e.target.getStage()?.getPointerPosition();
      if (!pos) return;

      const sx = rubberStart.current.x;
      const sy = rubberStart.current.y;
      setRubberBand({
        x: Math.min(sx, pos.x),
        y: Math.min(sy, pos.y),
        width: Math.abs(pos.x - sx),
        height: Math.abs(pos.y - sy),
        visible: true,
      });
    },
    [],
  );

  const handleStageMouseUp = useCallback(
    (_e: Konva.KonvaEventObject<MouseEvent>) => {
      if (!rubberStart.current) return;

      // Find objects intersecting the rubber band
      if (rubberBand.width > 5 || rubberBand.height > 5) {
        const rb = {
          x: rubberBand.x - offsetX,
          y: rubberBand.y - offsetY,
          width: rubberBand.width,
          height: rubberBand.height,
        };

        const intersecting = objects
          .filter((obj) => {
            if (!obj.visible || obj.locked) return false;
            const objW = getObjWidth(obj);
            const objH = getObjHeight(obj);
            return rectsIntersect(
              rb.x, rb.y, rb.width, rb.height,
              obj.x, obj.y, objW, objH,
            );
          })
          .map((obj) => obj.id);

        if (intersecting.length > 0) {
          onSelect(intersecting, false);
        }
      }

      rubberStart.current = null;
      setRubberBand({ x: 0, y: 0, width: 0, height: 0, visible: false });
    },
    [rubberBand, objects, offsetX, offsetY, onSelect],
  );

  return (
    <Stage
      ref={stageRef}
      width={width}
      height={height}
      onMouseDown={handleStageMouseDown}
      onMouseMove={handleStageMouseMove}
      onMouseUp={handleStageMouseUp}
      style={{ cursor: activeTool === 'pointer' ? 'default' : 'crosshair' }}
    >
      <Layer ref={layerRef}>
        {/* Artboard background */}
        {renderArtboard()}

        {/* Canvas objects */}
        {objects.map(renderObject)}

        {/* Transformer for selection handles */}
        <Transformer
          ref={transformerRef}
          flipEnabled={false}
          boundBoxFunc={(_oldBox, newBox) => {
            if (Math.abs(newBox.width) < 5 || Math.abs(newBox.height) < 5) {
              return _oldBox;
            }
            return newBox;
          }}
        />

        {/* Rubber-band selection rectangle */}
        {rubberBand.visible && (
          <Rect
            x={rubberBand.x}
            y={rubberBand.y}
            width={rubberBand.width}
            height={rubberBand.height}
            fill="rgba(170, 59, 255, 0.1)"
            stroke="rgba(170, 59, 255, 0.6)"
            strokeWidth={1}
            listening={false}
          />
        )}
      </Layer>
    </Stage>
  );
}

// -- KonvaImage wrapper (loads HTMLImageElement from src) --------------------

interface KonvaImageWrapperProps {
  id: string;
  src: string;
  x: number;
  y: number;
  width: number;
  height: number;
  rotation: number;
  opacity: number;
  draggable: boolean;
  onClick: (e: Konva.KonvaEventObject<MouseEvent>) => void;
  onDragEnd: (e: Konva.KonvaEventObject<DragEvent>) => void;
  onTransformEnd: (e: Konva.KonvaEventObject<Event>) => void;
}

function KonvaImageWrapper({
  src,
  ...restProps
}: KonvaImageWrapperProps) {
  const [image, setImage] = useState<HTMLImageElement | null>(null);

  useEffect(() => {
    const img = new window.Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => setImage(img);
    img.src = src;
    return () => {
      img.onload = null;
    };
  }, [src]);

  if (!image) return null;

  return <KonvaImage {...restProps} image={image} />;
}

// -- Geometry helpers ---------------------------------------------------------

function getObjWidth(obj: CanvasObject): number {
  switch (obj.type) {
    case 'rect':
    case 'image':
      return obj.width;
    case 'circle':
      return obj.radius * 2;
    case 'ellipse':
      return obj.radiusX * 2;
    case 'line':
      return Math.max(...obj.points.filter((_, i) => i % 2 === 0)) -
        Math.min(...obj.points.filter((_, i) => i % 2 === 0));
    case 'text':
      return obj.width || obj.fontSize * obj.text.length * 0.6;
  }
}

function getObjHeight(obj: CanvasObject): number {
  switch (obj.type) {
    case 'rect':
    case 'image':
      return obj.height;
    case 'circle':
      return obj.radius * 2;
    case 'ellipse':
      return obj.radiusY * 2;
    case 'line':
      return Math.max(...obj.points.filter((_, i) => i % 2 === 1)) -
        Math.min(...obj.points.filter((_, i) => i % 2 === 1));
    case 'text':
      return obj.fontSize * obj.lineHeight;
  }
}

function rectsIntersect(
  ax: number, ay: number, aw: number, ah: number,
  bx: number, by: number, bw: number, bh: number,
): boolean {
  return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}
