import { useEffect, useRef, useCallback } from 'react';
import { Layer, Rect, Line, Transformer } from 'react-konva';

/**
 * On-canvas crop overlay.
 *
 * Mounted by DesignCanvas when App's `cropping` state targets an image
 * element. Renders into its OWN dedicated Layer above the elements
 * layer — keeping it out of the elements layer means there's zero
 * z-order or hit-test ambiguity with image / text nodes or the shared
 * multi-select Transformer that lives down there. The Layer carries
 * the same `x`/`y` offset as the elements layer so the crop Rect's
 * x/y are in canonical 0..CANVAS_SIZE design space.
 *
 * Contents (in z-order, bottom up):
 *
 *   1. Four dim rects covering the parts of the IMAGE ELEMENT outside
 *      the current crop selection — the user sees what will be kept
 *      (bright) vs what will be discarded (dimmed).
 *
 *   2. Rule-of-thirds guide lines inside the crop rect.
 *
 *   3. The crop rect itself — transparent fill, pink stroke. NOT
 *      currently draggable; see the inline IMPORTANT note on the Rect
 *      below for the reason and a sketch of how interior-drag could
 *      be added back safely.
 *
 *   4. Konva Transformer attached to the rect. All 8 anchors enabled
 *      so the user can resize from corners or edges (including
 *      shrinking each side independently via the middle-edge anchors).
 *      `keepRatio` off — crop is about choosing composition, not
 *      preserving aspect.
 *
 * Coordinate-system note (the bug that took several iterations to find)
 * ────────────────────────────────────────────────────────────────────
 * Konva's Transformer calls `boundBoxFunc(oldBox, newBox)` with boxes
 * in ABSOLUTE STAGE COORDS, NOT in the layer-local coord system of
 * the attached node. This is undocumented in the prose of the Konva
 * docs (the docs just say "format: {x, y, width, height, rotation}"
 * without specifying the coord system), but it's empirically confirmed
 * by inspecting the box values during a drag: a Rect at layer-local
 * (86, 71) inside a Layer offset by (162, 216) produces newBox.x = 248.
 *
 * The element's x/y/width/height in `props.element` are in layer-local
 * design coords (App's state, same space as `cropping.rect`). To
 * compare newBox against the element's bounds, we must first convert
 * newBox from absolute to layer-local by subtracting layerX/layerY.
 * Earlier iterations skipped that conversion, so every drag failed
 * the "is the new box inside the element?" check by ~LAYER_OFFSET_X
 * units, and the Transformer rejected every newBox by returning
 * oldBox each frame — visible symptom: anchors didn't move on drag.
 *
 * Why Konva primitives instead of a dedicated crop library
 * ─────────────────────────────────────────────────────────
 * The editor is already 100% Konva. Bringing in something like
 * react-image-crop or cropperjs would mean rendering the image in
 * the DOM during crop mode (those libs operate on <img>) and then
 * synchronizing the crop result back to the Konva element — two
 * rendering paths for the same element. Konva's own Rect + Transformer
 * give us the exact UI with zero extra code or sync, and the result
 * (a `crop` attr on Konva.Image) is already part of the schema.
 */
export function CropOverlay({
  element,
  cropRect,
  onCropRectChange,
  layerX,
  layerY,
}) {
  const rectRef = useRef(null);
  const trRef = useRef(null);

  // Wire the Transformer to the rect after both have mounted. Pure
  // useEffect pattern — runs once after the first render commits
  // both nodes to the Konva tree. Empty deps because re-running
  // would call nodes([rect]) again with the same node (no-op).
  useEffect(() => {
    const transformer = trRef.current;
    const shape = rectRef.current;
    if (!transformer || !shape) return;
    transformer.nodes([shape]);
    transformer.getLayer()?.batchDraw();
  }, []);

  // Transform bound — keep the rect inside the element's bbox while
  // resizing via the anchors.
  //
  // newBox is in ABSOLUTE STAGE COORDS (see the coordinate-system
  // note in the module docblock). We convert to layer-local by
  // subtracting the layer offset, then compare against the element's
  // layer-local bounds.
  //
  // EPS of 5 is generous to handle:
  //   • Sub-pixel artifacts in Konva's continuous transform math.
  //   • The 1.5px stroke half-width that, even with ignoreStroke on
  //     the Transformer, can produce a fraction of a unit's
  //     discrepancy between the Rect's bbox and its content area.
  const boundBoxFunc = useCallback((oldBox, newBox) => {
    const EPS = 5;
    const MIN_CROP = 12;
    if (newBox.width < MIN_CROP || newBox.height < MIN_CROP) return oldBox;

    // Convert from absolute stage coords to layer-local design coords.
    // The element's x/y/width/height are in the same layer-local space.
    const localX = newBox.x - layerX;
    const localY = newBox.y - layerY;
    const localRight = localX + newBox.width;
    const localBottom = localY + newBox.height;

    if (
      localX < element.x - EPS
      || localY < element.y - EPS
      || localRight > element.x + element.width + EPS
      || localBottom > element.y + element.height + EPS
    ) return oldBox;
    return newBox;
  }, [element.x, element.y, element.width, element.height, layerX, layerY]);

  // Commit geometry changes back up to App. Called on transform-end
  // (continuous updates would be over-eager — the overlay is purely
  // visual until the user clicks Apply). Bakes the Transformer's
  // scale into width/height so subsequent transforms start from a
  // clean ±1 scale — same pattern as ImageElement.
  //
  // node.x() and node.y() return layer-local coords, which is what
  // App's cropping.rect state expects.
  const commitGeometry = useCallback(() => {
    const node = rectRef.current;
    if (!node) return;
    const sx = node.scaleX();
    const sy = node.scaleY();
    const w = Math.max(12, node.width() * Math.abs(sx));
    const h = Math.max(12, node.height() * Math.abs(sy));
    node.scaleX(1);
    node.scaleY(1);
    node.width(w);
    node.height(h);
    onCropRectChange({
      x: node.x(),
      y: node.y(),
      width: w,
      height: h,
    });
  }, [onCropRectChange]);

  // Geometry shortcuts for the dim mask + guides.
  const ex = element.x;
  const ey = element.y;
  const ew = element.width;
  const eh = element.height;
  const cx = cropRect.x;
  const cy = cropRect.y;
  const cw = cropRect.width;
  const ch = cropRect.height;

  const MASK_FILL = 'rgba(15, 23, 42, 0.55)'; // slate-900 at low opacity

  return (
    <Layer x={layerX} y={layerY}>
      {/* Dim mask — top strip. */}
      <Rect
        x={ex}
        y={ey}
        width={ew}
        height={Math.max(0, cy - ey)}
        fill={MASK_FILL}
        listening={false}
        perfectDrawEnabled={false}
      />
      {/* Dim mask — bottom strip. */}
      <Rect
        x={ex}
        y={cy + ch}
        width={ew}
        height={Math.max(0, (ey + eh) - (cy + ch))}
        fill={MASK_FILL}
        listening={false}
        perfectDrawEnabled={false}
      />
      {/* Dim mask — left strip. */}
      <Rect
        x={ex}
        y={cy}
        width={Math.max(0, cx - ex)}
        height={ch}
        fill={MASK_FILL}
        listening={false}
        perfectDrawEnabled={false}
      />
      {/* Dim mask — right strip. */}
      <Rect
        x={cx + cw}
        y={cy}
        width={Math.max(0, (ex + ew) - (cx + cw))}
        height={ch}
        fill={MASK_FILL}
        listening={false}
        perfectDrawEnabled={false}
      />

      {/* Rule-of-thirds guide lines. */}
      <Line
        points={[cx + cw / 3, cy, cx + cw / 3, cy + ch]}
        stroke="rgba(255, 255, 255, 0.55)"
        strokeWidth={1}
        dash={[4, 4]}
        listening={false}
        perfectDrawEnabled={false}
      />
      <Line
        points={[cx + (cw * 2) / 3, cy, cx + (cw * 2) / 3, cy + ch]}
        stroke="rgba(255, 255, 255, 0.55)"
        strokeWidth={1}
        dash={[4, 4]}
        listening={false}
        perfectDrawEnabled={false}
      />
      <Line
        points={[cx, cy + ch / 3, cx + cw, cy + ch / 3]}
        stroke="rgba(255, 255, 255, 0.55)"
        strokeWidth={1}
        dash={[4, 4]}
        listening={false}
        perfectDrawEnabled={false}
      />
      <Line
        points={[cx, cy + (ch * 2) / 3, cx + cw, cy + (ch * 2) / 3]}
        stroke="rgba(255, 255, 255, 0.55)"
        strokeWidth={1}
        dash={[4, 4]}
        listening={false}
        perfectDrawEnabled={false}
      />

      {/* The crop rect itself. Transparent fill + pink stroke.
       *
       * IMPORTANT — the Rect is intentionally NOT draggable.
       *
       * Earlier iterations had `draggable={true}` plus a generous
       * `hitStrokeWidth={20}` so the user could grab the rect's
       * border to translate the whole crop region. That combination
       * overlapped the rect's hit area with the Transformer's anchor
       * hit areas at the corners; Konva's hit-test gave inconsistent
       * results between anchor-resize and rect-drag.
       *
       * For now the user shrinks the crop region by dragging each
       * edge inward via its middle-edge anchor. Translating the
       * whole crop would need a separate inner Rect with its own
       * listening / draggable, sized to NOT overlap the anchor
       * positions — worthwhile as a follow-up if users miss the
       * interior-drag affordance. */}
      <Rect
        ref={rectRef}
        x={cropRect.x}
        y={cropRect.y}
        width={cropRect.width}
        height={cropRect.height}
        fill="rgba(0, 0, 0, 0.01)"
        stroke="#ec4899"
        strokeWidth={1.5}
        onTransformEnd={commitGeometry}
      />

      {/* Transformer — 8 anchors (default), no rotation, no keepRatio.
       * `ignoreStroke` keeps the anchor positions tracking the rect's
       * content geometry rather than the stroke-inflated bbox; without
       * it, the anchors render 0.75px outside each edge and the
       * resize feel is slightly off. */}
      <Transformer
        ref={trRef}
        rotateEnabled={false}
        keepRatio={false}
        boundBoxFunc={boundBoxFunc}
        anchorFill="#fff"
        anchorStroke="#ec4899"
        anchorSize={12}
        anchorCornerRadius={2}
        borderEnabled={false}
        ignoreStroke
      />
    </Layer>
  );
}
