import React, { forwardRef, useCallback, useMemo } from 'react';
import { RotateCcw, LayoutGrid, ZoomIn, ZoomOut } from '../../icons';
import NetBox from './NetBox';
import NodeCard from './NodeCard';
import type { Net, MazeNode, Edge } from './types';
import type { Selection } from './Inspector';
import type { ResizeHandle } from './useMazeInteraction';

interface Props {
  nets: Net[];
  nodes: MazeNode[];
  edges: Edge[];
  deployed: boolean;
  selection: Selection;
  setSelection: (s: Selection) => void;
  pan: { x: number; y: number };
  zoom: number;
  dropTargetId: string | null;
  dragging: boolean;
  edgeDraw: { fromX: number; fromY: number; toX: number; toY: number; hoverTarget: string | null } | null;
  onCanvasMouseDown: (e: React.MouseEvent) => void;
  onNodeMouseDown: (id: string) => (e: React.MouseEvent) => void;
  onNetMouseDown: (id: string) => (e: React.MouseEvent) => void;
  onNetResizeMouseDown: (id: string, handle: ResizeHandle) => (e: React.MouseEvent) => void;
  onPortMouseDown: (id: string) => (e: React.MouseEvent) => void;
  onNodeContextMenu?: (id: string) => (e: React.MouseEvent) => void;
  onNetContextMenu?: (id: string) => (e: React.MouseEvent) => void;
  onEdgeContextMenu?: (id: string) => (e: React.MouseEvent) => void;
  onCanvasContextMenu?: (e: React.MouseEvent) => void;
  onResetView?: () => void;
  onAutoLayout?: () => void;
  onZoomIn?: () => void;
  onZoomOut?: () => void;
  sseConnected?: boolean;
  lastEventAt?: Date | null;
  onSelectService?: (nodeId: string, slug: string) => void;
  panLayerRef?: React.RefObject<HTMLDivElement | null>;
  gridPatternRef?: React.RefObject<SVGPatternElement | null>;
}

const fmtTime = (d: Date) =>
  `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;

const NODE_W = 140;
const NODE_HEAD_H = 22;

const Canvas = forwardRef<HTMLDivElement, Props>(function Canvas(
  { nets, nodes, edges, deployed, selection, setSelection, pan, zoom, dropTargetId, dragging, edgeDraw,
    onCanvasMouseDown, onNodeMouseDown, onNetMouseDown, onNetResizeMouseDown, onPortMouseDown,
    onNodeContextMenu, onNetContextMenu, onEdgeContextMenu, onCanvasContextMenu,
    onResetView, onAutoLayout, onZoomIn, onZoomOut, sseConnected, lastEventAt, onSelectService,
    panLayerRef, gridPatternRef },
  ref,
) {
  const netById = useMemo(() => new Map(nets.map((n) => [n.id, n])), [nets]);
  // Pre-indexed node lookup so edge rendering is O(E) instead of
  // O(E·N) from the prior `nodes.find(...)` inside the edge loop.
  const nodeById = useMemo(() => new Map(nodes.map((n) => [n.id, n])), [nodes]);

  const absPos = (node: MazeNode) => {
    const net = netById.get(node.netId);
    return { x: (net?.x ?? 0) + node.x, y: (net?.y ?? 0) + node.y };
  };

  // Stable per-kind selection callbacks so React.memo on children
  // (NetBox/NodeCard) can actually short-circuit re-renders instead
  // of seeing a fresh closure on every Canvas render.
  const selectNet = useCallback((id: string) => setSelection({ type: 'net', id }), [setSelection]);
  const selectNode = useCallback((id: string) => setSelection({ type: 'node', id }), [setSelection]);

  // Flowing-dash edge animation is the single most expensive thing
  // on the canvas — each animated <path> invalidates its bounding
  // box every frame, and inter-LAN paths are long so the invalidated
  // rects overlap most of the viewport. Past ~60 edges the compositor
  // spends every frame repainting. Drop the animation class above
  // the threshold; edges stay fully visible, just static.
  const ANIMATE_EDGE_LIMIT = 60;
  const animateEdges = edges.length <= ANIMATE_EDGE_LIMIT;

  const activeNetIds = useMemo(() => {
    const nodeNet = new Map(nodes.map((n) => [n.id, n.netId]));
    const ids = new Set<string>();
    for (const e of edges) {
      const a = nodeNet.get(e.from); const b = nodeNet.get(e.to);
      if (a) ids.add(a); if (b) ids.add(b);
    }
    return ids;
  }, [nodes, edges]);

  const selNetId  = selection?.type === 'net'  ? selection.id : null;
  const selNodeId = selection?.type === 'node' ? selection.id
                    : selection?.type === 'service' ? selection.nodeId : null;
  const selEdgeId = selection?.type === 'edge' ? selection.id : null;
  const selServiceNodeId = selection?.type === 'service' ? selection.nodeId : null;
  const selServiceSlug = selection?.type === 'service' ? selection.id : null;

  return (
    <div
      ref={ref}
      className="maze-canvas-wrap"
      onMouseDown={(e) => {
        if (e.target === e.currentTarget) setSelection(null);
        onCanvasMouseDown(e);
      }}
      onContextMenu={(e) => {
        if (e.target === e.currentTarget && onCanvasContextMenu) onCanvasContextMenu(e);
      }}
      style={{ cursor: dragging ? 'grabbing' : 'grab' }}
    >
      <div className="maze-grid-bg">
        <svg xmlns="http://www.w3.org/2000/svg">
          <defs>
            <pattern
              ref={gridPatternRef ?? null}
              id="maze-grid-pat"
              x={pan.x}
              y={pan.y}
              width={40 * zoom}
              height={40 * zoom}
              patternUnits="userSpaceOnUse"
            >
              <path
                d={`M ${40 * zoom} 0 L 0 0 0 ${40 * zoom}`}
                fill="none"
                stroke="var(--grid-line)"
                strokeWidth="1"
              />
            </pattern>
          </defs>
          <rect width="100%" height="100%" fill="url(#maze-grid-pat)" />
        </svg>
      </div>

      <div
        ref={panLayerRef ?? null}
        className="maze-pan-layer"
        style={{
          transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
          transformOrigin: '0 0',
        }}
      >
        <svg className="maze-svg" overflow="visible">
          <defs>
            <marker id="arrow-matrix" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
              <path d="M0,0 L10,5 L0,10 z" fill="currentColor" style={{ color: 'var(--matrix)' }} />
            </marker>
            <marker id="arrow-violet" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
              <path d="M0,0 L10,5 L0,10 z" fill="currentColor" style={{ color: 'var(--violet)' }} />
            </marker>
            <marker id="arrow-alert" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto">
              <path d="M0,0 L10,5 L0,10 z" fill="#ff4141" />
            </marker>
          </defs>
          {edges.map((e) => {
            const from = nodeById.get(e.from);
            const to   = nodeById.get(e.to);
            if (!from || !to) return null;
            const a = absPos(from); const b = absPos(to);
            const x1 = a.x + NODE_W, y1 = a.y + NODE_HEAD_H;
            const x2 = b.x,           y2 = b.y + NODE_HEAD_H;
            const cx = (x1 + x2) / 2;
            const d  = `M${x1},${y1} C${cx},${y1} ${cx},${y2} ${x2},${y2}`;
            const klass  = e.traffic === 'hot' ? 'hot' : e.traffic === 'active' ? 'active' : '';
            const marker = e.traffic === 'hot' ? 'arrow-alert' : e.traffic === 'active' ? 'arrow-violet' : 'arrow-matrix';
            const isSel  = e.id === selEdgeId;
            return (
              <g key={e.id} style={{ pointerEvents: 'auto' }}
                 onClick={(ev) => { ev.stopPropagation(); setSelection({ type: 'edge', id: e.id }); }}
                 onContextMenu={onEdgeContextMenu?.(e.id)}>
                <path d={d} className={`maze-edge ${klass} ${animateEdges ? 'maze-edge-dash' : ''}`} markerEnd={`url(#${marker})`}
                      style={{ strokeWidth: isSel ? 2.5 : 1.5 }} />
                <path d={d} stroke="transparent" strokeWidth="12" fill="none" style={{ cursor: 'pointer' }} />
                {e.label && (
                  <text x={cx} y={(y1 + y2) / 2 - 6} textAnchor="middle"
                        fill={e.traffic === 'hot' ? '#ff4141' : '#ee82ee'}
                        fontSize="9" fontFamily="var(--font-mono)" letterSpacing="1">
                    {e.label}
                  </text>
                )}
              </g>
            );
          })}
          {edgeDraw && (() => {
            const cx = (edgeDraw.fromX + edgeDraw.toX) / 2;
            const d = `M${edgeDraw.fromX},${edgeDraw.fromY} C${cx},${edgeDraw.fromY} ${cx},${edgeDraw.toY} ${edgeDraw.toX},${edgeDraw.toY}`;
            return <path d={d} className={`ghost-edge ${edgeDraw.hoverTarget ? 'snap' : ''}`} />;
          })()}
        </svg>

        <div className="maze-nodes">
          {nets.map((net) => {
            const inactive = net.kind !== 'internet' && !activeNetIds.has(net.id);
            return (
              <NetBox
                key={net.id}
                net={net}
                selected={net.id === selNetId}
                dropTarget={dropTargetId === net.id}
                inactive={inactive}
                deployed={deployed}
                onSelect={selectNet}
                onHeaderMouseDown={onNetMouseDown}
                onResizeMouseDown={onNetResizeMouseDown}
                onContextMenu={onNetContextMenu?.(net.id)}
              />
            );
          })}
          {nodes.map((n) => {
            const p = absPos(n);
            return (
              <NodeCard
                key={n.id}
                node={n}
                absX={p.x}
                absY={p.y}
                selected={n.id === selNodeId}
                deployed={deployed}
                dragging={dragging && n.id === selNodeId}
                selectedServiceSlug={n.id === selServiceNodeId ? selServiceSlug : null}
                onSelect={selectNode}
                onSelectService={onSelectService}
                onMouseDown={onNodeMouseDown}
                onPortMouseDown={onPortMouseDown}
                onContextMenu={onNodeContextMenu}
              />
            );
          })}
        </div>
      </div>

      {(onResetView || onAutoLayout || onZoomIn || onZoomOut) && (
        <div className="maze-toolbar">
          {onResetView && (
            <button type="button" className="maze-btn ghost small" onClick={onResetView} title="Reset pan + zoom">
              <RotateCcw size={11} /> RESET VIEW
            </button>
          )}
          {onAutoLayout && (
            <button type="button" className="maze-btn ghost small" onClick={onAutoLayout} title="Auto-layout nodes">
              <LayoutGrid size={11} /> AUTO-LAYOUT
            </button>
          )}
          {onZoomOut && (
            <button type="button" className="maze-btn ghost small" onClick={onZoomOut} title="Zoom out">
              <ZoomOut size={11} />
            </button>
          )}
          {onZoomIn && (
            <button type="button" className="maze-btn ghost small" onClick={onZoomIn} title="Zoom in">
              <ZoomIn size={11} />
            </button>
          )}
        </div>
      )}

      <div className="maze-status">
        <span className={`status-seg ${sseConnected ? 'live' : 'dim'}`}>
          <span className={`status-dot ${sseConnected ? 'active' : 'idle'}`} />
          GRAPH {sseConnected ? 'LIVE' : 'IDLE'}
        </span>
        <span className="status-seg">PAN: {Math.round(pan.x)},{Math.round(pan.y)}</span>
        <span className="status-seg">ZOOM: {Math.round(zoom * 100)}%</span>
        <span className="status-seg">AS-OF {lastEventAt ? fmtTime(lastEventAt) : '--:--:--'}</span>
        {!animateEdges && (
          <span className="status-seg" title={`Flow animation auto-disabled above ${ANIMATE_EDGE_LIMIT} edges (${edges.length} active) to keep the canvas responsive.`}>
            MOTION: OFF
          </span>
        )}
      </div>

      <div className="maze-legend">
        <div className="lg-row"><span className="lg-swatch alert" /> ACTIVE ATTACK</div>
        <div className="lg-row"><span className="lg-swatch violet" /> OBSERVED FLOW</div>
        <div className="lg-row"><span className="lg-swatch matrix" /> CONFIGURED</div>
        <div className="lg-row"><span className="lg-swatch inactive" /> INACTIVE NET</div>
      </div>
    </div>
  );
});

export default Canvas;
