import React, { useCallback, useEffect, useRef, useState } from "react";
import {
  clampCanvasItem,
  resizeCanvasItemFromSe,
} from "../../utils/pictureCanvas.js";

function itemId(item) {
  return item?.id != null ? String(item.id) : "";
}

const CORNER_HIT_MIN_PX = 22;
const CORNER_HIT_RATIO = 0.3;

/** Bottom-right corner of the image is the resize handle; everything else moves. */
function interactionModeFromPointer(event, element) {
  const rect = element?.getBoundingClientRect?.();
  if (!rect?.width || !rect?.height) return "move";

  const x = event.clientX - rect.left;
  const y = event.clientY - rect.top;
  const corner = Math.max(
    CORNER_HIT_MIN_PX,
    Math.min(rect.width, rect.height) * CORNER_HIT_RATIO
  );
  const nearRight = x >= rect.width - corner;
  const nearBottom = y >= rect.height - corner;
  return nearRight && nearBottom ? "resize" : "move";
}

function cursorForMode(mode) {
  return mode === "resize" ? "nwse-resize" : "move";
}

/**
 * Page-level picture canvas: positioned, resizable images independent of content rows.
 * The parent owns the `items` array; this component reports changes through `onChange`.
 * Coordinates are always 0–100% of the full picture cell (including merged row spans).
 */
export default function PictureCanvasColumn({
  items = [],
  readOnly = false,
  rowMode = false,
  stageHeightPx = null,
  mergeWithNext = false,
  canMergeWithNext = false,
  onMergeWithNextChange,
  onChange,
  onAddImage,
  onEditImage,
  onClearAll,
}) {
  const stageRef = useRef(null);
  const fileInputRef = useRef(null);
  const dragRef = useRef(null);
  const rafRef = useRef(null);
  const [dragItems, setDragItems] = useState(null);
  const [activeId, setActiveId] = useState(null);

  const onChangeRef = useRef(onChange);
  onChangeRef.current = onChange;

  const displayItems = dragItems ?? items;

  const displayItemsRef = useRef(displayItems);
  displayItemsRef.current = displayItems;

  const commitItems = useCallback((nextItems) => {
    onChangeRef.current?.(nextItems);
  }, []);

  const patchItem = useCallback((list, id, patch) => {
    const targetId = String(id);
    return list.map((item) =>
      itemId(item) === targetId ? clampCanvasItem({ ...item, ...patch }) : item
    );
  }, []);

  const bringToFront = useCallback(
    (list, id) => {
      const maxZ = list.reduce(
        (max, item) => Math.max(max, Number(item.z_index) || 0),
        0
      );
      return patchItem(list, id, { z_index: maxZ + 1 });
    },
    [patchItem]
  );

  const removeItem = useCallback(
    (id) => {
      const targetId = String(id);
      const nextItems = displayItemsRef.current.filter(
        (item) => itemId(item) !== targetId
      );
      commitItems(nextItems);
    },
    [commitItems]
  );

  const applyDragFrame = useCallback(() => {
    rafRef.current = null;
    const drag = dragRef.current;
    if (!drag?.pending) return;
    const { clientX, clientY, shiftKey } = drag.pending;
    drag.pending = null;

    const dxPct = ((clientX - drag.startX) / drag.rect.width) * 100;
    const dyPct = ((clientY - drag.startY) / drag.rect.height) * 100;

    let next;
    if (drag.mode === "move") {
      next = patchItem(drag.workingItems, drag.id, {
        x_pct: drag.origin.x_pct + dxPct,
        y_pct: drag.origin.y_pct + dyPct,
      });
    } else {
      const targetId = String(drag.id);
      next = drag.workingItems.map((item) => {
        if (itemId(item) !== targetId) return item;
        return resizeCanvasItemFromSe(
          item,
          drag.origin,
          dxPct,
          dyPct,
          !shiftKey
        );
      });
    }

    drag.workingItems = next;
    setDragItems(next);
  }, [patchItem]);

  const onPointerMove = useCallback(
    (event) => {
      const drag = dragRef.current;
      if (!drag) return;
      drag.pending = {
        clientX: event.clientX,
        clientY: event.clientY,
        shiftKey: event.shiftKey,
      };
      if (rafRef.current == null) {
        rafRef.current = requestAnimationFrame(applyDragFrame);
      }
    },
    [applyDragFrame]
  );

  const endDrag = useCallback(() => {
    const drag = dragRef.current;
    if (!drag) return;

    if (rafRef.current != null) {
      cancelAnimationFrame(rafRef.current);
      rafRef.current = null;
      if (drag.pending) applyDragFrame();
    }

    dragRef.current = null;
    document.body.style.cursor = "";
    window.removeEventListener("pointermove", onPointerMove);
    window.removeEventListener("pointerup", endDrag);
    window.removeEventListener("pointercancel", endDrag);

    if (drag.workingItems) {
      commitItems(drag.workingItems);
    }
    setDragItems(null);
    setActiveId(null);
  }, [onPointerMove, applyDragFrame, commitItems]);

  const startDrag = useCallback(
    (event, item, mode) => {
      if (readOnly) return;
      event.preventDefault();
      event.stopPropagation();
      const rect = stageRef.current?.getBoundingClientRect();
      if (!rect || rect.width <= 0 || rect.height <= 0) return;

      const id = itemId(item);
      if (!id) return;

      const workingItems = bringToFront([...displayItemsRef.current], id);

      try {
        event.currentTarget.setPointerCapture?.(event.pointerId);
      } catch {
        /* ignore capture failures */
      }

      dragRef.current = {
        id,
        mode,
        startX: event.clientX,
        startY: event.clientY,
        origin: {
          x_pct: Number(item.x_pct),
          y_pct: Number(item.y_pct),
          w_pct: Number(item.w_pct),
          h_pct: Number(item.h_pct),
        },
        rect,
        workingItems,
        pending: null,
      };
      document.body.style.cursor = cursorForMode(mode);
      setActiveId(id);
      setDragItems(workingItems);
      window.addEventListener("pointermove", onPointerMove);
      window.addEventListener("pointerup", endDrag);
      window.addEventListener("pointercancel", endDrag);
    },
    [readOnly, bringToFront, onPointerMove, endDrag]
  );

  useEffect(() => {
    if (dragRef.current) return;
    setDragItems(null);
  }, [items]);

  useEffect(
    () => () => {
      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
      window.removeEventListener("pointermove", onPointerMove);
      window.removeEventListener("pointerup", endDrag);
      window.removeEventListener("pointercancel", endDrag);
    },
    [onPointerMove, endDrag]
  );

  const handleFile = (event) => {
    const file = event.target.files?.[0];
    if (file) onAddImage?.(file);
    event.target.value = "";
  };

  const stopPropagation = (event) => event.stopPropagation();

  const handleItemPointerMove = useCallback((event) => {
    if (readOnly || dragRef.current) return;
    const mode = interactionModeFromPointer(event, event.currentTarget);
    event.currentTarget.style.cursor = cursorForMode(mode);
  }, [readOnly]);

  const handleItemPointerLeave = useCallback((event) => {
    event.currentTarget.style.cursor = "";
  }, []);

  const handleItemPointerDown = useCallback(
    (event, item) => {
      if (readOnly) return;
      if (event.target.closest(".doc-picture-canvas__item-actions")) return;
      const mode = interactionModeFromPointer(event, event.currentTarget);
      startDrag(event, item, mode);
    },
    [readOnly, startDrag]
  );

  const stageStyle =
    stageHeightPx != null && Number.isFinite(Number(stageHeightPx))
      ? {
          height: `${Number(stageHeightPx)}px`,
          minHeight: `${Number(stageHeightPx)}px`,
          maxHeight: `${Number(stageHeightPx)}px`,
          flex: "0 0 auto",
        }
      : undefined;

  return (
    <div
      className={`doc-picture-canvas${readOnly ? " doc-picture-canvas--view" : ""}${
        rowMode ? " doc-picture-canvas--row" : ""
      }`}
    >
      {!readOnly ? (
        <div className="doc-picture-canvas__toolbar">
          <span className="doc-picture-canvas__title">Pictures</span>
          <div className="doc-picture-canvas__toolbar-actions">
            {canMergeWithNext ? (
              <label className="doc-picture-canvas__merge">
                <input
                  type="checkbox"
                  checked={mergeWithNext}
                  onChange={(event) => onMergeWithNextChange?.(event.target.checked)}
                />
                <span>Merge with next row</span>
              </label>
            ) : null}
            <button
              type="button"
              className="swi-btn swi-btn--ghost swi-btn--compact"
              onClick={() => fileInputRef.current?.click()}
            >
              + Add picture
            </button>
            <button
              type="button"
              className="swi-btn swi-btn--ghost swi-btn--compact"
              onClick={onClearAll}
              disabled={!items.length}
            >
              Clear all
            </button>
          </div>
          <input
            ref={fileInputRef}
            type="file"
            accept="image/*"
            className="doc-picture-canvas__file"
            onChange={handleFile}
          />
        </div>
      ) : null}

      <div className="doc-picture-canvas__stage" ref={stageRef} style={stageStyle}>
        {displayItems.length === 0 && !readOnly ? (
          <div className="doc-picture-canvas__empty">
            No pictures yet. Use “Add picture”.
          </div>
        ) : null}

        {displayItems.map((item) => {
          const id = itemId(item);
          const isActive = activeId != null && id === activeId;
          return (
            <div
              key={id || `pic-${item.label}`}
              className={`doc-picture-canvas__item${
                isActive ? " doc-picture-canvas__item--active" : ""
              }`}
              style={{
                left: `${item.x_pct}%`,
                top: `${item.y_pct}%`,
                width: `${item.w_pct}%`,
                height: `${item.h_pct}%`,
                zIndex: item.z_index || 1,
              }}
              title={
                readOnly
                  ? undefined
                  : "Drag image to move. Drag the bottom-right corner to resize (hold Shift for free resize)."
              }
              onPointerDown={(event) => handleItemPointerDown(event, item)}
              onPointerMove={handleItemPointerMove}
              onPointerLeave={handleItemPointerLeave}
            >
              <img
                src={item.image_data_url}
                alt={`Picture ${item.label}`}
                className="doc-picture-canvas__img"
                draggable={false}
              />
              <span className="doc-picture-canvas__label">Pic {item.label}</span>
              {!readOnly ? (
                <div
                  className="doc-picture-canvas__item-actions"
                  onPointerDown={stopPropagation}
                >
                  <button
                    type="button"
                    className="swi-btn swi-btn--ghost btn-small"
                    onClick={() => onEditImage?.(item.id)}
                  >
                    Edit
                  </button>
                  <button
                    type="button"
                    className="swi-btn swi-btn--ghost btn-small"
                    onClick={() => removeItem(item.id)}
                  >
                    Remove
                  </button>
                </div>
              ) : null}
            </div>
          );
        })}
      </div>
    </div>
  );
}
