import { useCallback, useEffect, useRef, useState } from 'react';
import { Box, Paper, Text } from '@mantine/core';
import { IconPhoto } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import type { TemplateFieldPlacement, TemplateLogoPlacement } from '@ema-platform/api';

interface Props {
  backgroundUrl: string;
  logoUrl: string;
  logoPlacement: TemplateLogoPlacement;
  landscape: boolean;
  pageWidth?: string;
  pageHeight?: string;
  placements: TemplateFieldPlacement[];
  selectedId: string | null;
  onSelect: (id: string | null) => void;
  onChange: (placements: TemplateFieldPlacement[]) => void;
  disabled: boolean;
}

/** A4 aspect ratio, the fallback for a version with no custom page size. */
const A4_RATIO = 297 / 210;

/** Parses a CSS length like "4.92in" or "125mm" into a unitless number, unit-agnostic — only the ratio between width and height matters here. */
function parseLength(value: string): number | null {
  const match = value.trim().match(/^([\d.]+)/);
  if (!match) return null;
  const n = Number(match[1]);
  return Number.isFinite(n) && n > 0 ? n : null;
}

const LOGO_CORNER_STYLE: Record<string, (offset: number) => React.CSSProperties> = {
  TOP_LEFT: (o) => ({ top: `${o}%`, left: `${o}%` }),
  TOP_CENTER: (o) => ({ top: `${o}%`, left: '50%', transform: 'translateX(-50%)' }),
  TOP_RIGHT: (o) => ({ top: `${o}%`, right: `${o}%` }),
  BOTTOM_LEFT: (o) => ({ bottom: `${o}%`, left: `${o}%` }),
  BOTTOM_RIGHT: (o) => ({ bottom: `${o}%`, right: `${o}%` }),
};

type DragState = {
  id: string;
  mode: 'move' | 'resize';
  pointerId: number;
  /** Grab offset within the block, in percent, so it does not jump on grab. */
  grabDxPct: number;
  grabDyPct: number;
  startWidthPct: number;
  startXPct: number;
};

/**
 * The visual certificate editor.
 *
 * Blocks are positioned in percentages of the page box, which is what lets the
 * same layout render correctly in both orientations and at any zoom: the
 * canvas scales with its container, and the compiled Handlebars uses the same
 * percentages against the real A4 page.
 *
 * Pointer events rather than HTML5 drag-and-drop — the latter cannot report
 * continuous positions during a drag, and offers no path to resizing.
 */
export function TemplateCanvas({
  backgroundUrl,
  logoUrl,
  logoPlacement,
  landscape,
  pageWidth,
  pageHeight,
  placements,
  selectedId,
  onSelect,
  onChange,
  disabled,
}: Props) {
  const { t } = useTranslation();
  const pageRef = useRef<HTMLDivElement>(null);
  const [drag, setDrag] = useState<DragState | null>(null);

  // Held in a ref as well: the pointer handlers are bound to the window for the
  // life of a drag, and would otherwise close over a stale placements array.
  const placementsRef = useRef(placements);
  placementsRef.current = placements;

  const pointToPct = useCallback((clientX: number, clientY: number) => {
    const rect = pageRef.current?.getBoundingClientRect();
    if (!rect || rect.width === 0 || rect.height === 0) return { xPct: 0, yPct: 0 };
    return {
      xPct: ((clientX - rect.left) / rect.width) * 100,
      yPct: ((clientY - rect.top) / rect.height) * 100,
    };
  }, []);

  useEffect(() => {
    if (!drag) return;

    function handleMove(event: PointerEvent) {
      if (!drag) return;
      const { xPct, yPct } = pointToPct(event.clientX, event.clientY);

      onChange(
        placementsRef.current.map((block) => {
          if (block.id !== drag.id) return block;

          if (drag.mode === 'resize') {
            // Width follows the pointer's distance from the block's left edge,
            // clamped so a block can neither invert nor leave the page.
            const width = Math.min(
              100 - drag.startXPct,
              Math.max(5, xPct - drag.startXPct),
            );
            return { ...block, widthPct: Number(width.toFixed(2)) };
          }

          const nextX = Math.min(
            100 - block.widthPct,
            Math.max(0, xPct - drag.grabDxPct),
          );
          const nextY = Math.min(99, Math.max(0, yPct - drag.grabDyPct));
          return {
            ...block,
            xPct: Number(nextX.toFixed(2)),
            yPct: Number(nextY.toFixed(2)),
          };
        }),
      );
    }

    function handleUp() {
      setDrag(null);
    }

    window.addEventListener('pointermove', handleMove);
    window.addEventListener('pointerup', handleUp);
    window.addEventListener('pointercancel', handleUp);
    return () => {
      window.removeEventListener('pointermove', handleMove);
      window.removeEventListener('pointerup', handleUp);
      window.removeEventListener('pointercancel', handleUp);
    };
  }, [drag, onChange, pointToPct]);

  function startDrag(
    event: React.PointerEvent,
    block: TemplateFieldPlacement,
    mode: 'move' | 'resize',
  ) {
    if (disabled) return;
    event.preventDefault();
    event.stopPropagation();
    onSelect(block.id);
    const { xPct, yPct } = pointToPct(event.clientX, event.clientY);
    setDrag({
      id: block.id,
      mode,
      pointerId: event.pointerId,
      grabDxPct: xPct - block.xPct,
      grabDyPct: yPct - block.yPct,
      startWidthPct: block.widthPct,
      startXPct: block.xPct,
    });
  }

  /** Arrow keys nudge the selected block, for placement finer than a drag. */
  function handleKeyDown(event: React.KeyboardEvent, block: TemplateFieldPlacement) {
    if (disabled) return;
    const step = event.shiftKey ? 5 : 0.5;
    const deltas: Record<string, [number, number]> = {
      ArrowLeft: [-step, 0],
      ArrowRight: [step, 0],
      ArrowUp: [0, -step],
      ArrowDown: [0, step],
    };
    const delta = deltas[event.key];
    if (!delta) return;
    event.preventDefault();
    onChange(
      placements.map((candidate) =>
        candidate.id === block.id
          ? {
              ...candidate,
              xPct: Number(
                Math.min(100 - candidate.widthPct, Math.max(0, candidate.xPct + delta[0])).toFixed(2),
              ),
              yPct: Number(Math.min(99, Math.max(0, candidate.yPct + delta[1])).toFixed(2)),
            }
          : candidate,
      ),
    );
  }

  const corner = logoPlacement.corner ?? 'TOP_LEFT';
  const logoStyle = (LOGO_CORNER_STYLE[corner] ?? LOGO_CORNER_STYLE.TOP_LEFT)(
    logoPlacement.offsetPct ?? 5,
  );

  // A custom size already states its own orientation (4.92in × 3.46in is
  // landscape on its own), so it is used as-is rather than flipped again by
  // `landscape` — that flag only disambiguates the A4 fallback below.
  const customWidth = pageWidth ? parseLength(pageWidth) : null;
  const customHeight = pageHeight ? parseLength(pageHeight) : null;
  const aspectRatio =
    customWidth && customHeight
      ? customWidth / customHeight
      : landscape
        ? A4_RATIO
        : 1 / A4_RATIO;

  return (
    <Paper withBorder p="sm" radius="md">
      <Text size="xs" c="dimmed" mb="xs">
        {t(
          'designer.canvasHint',
          'Drag a block to move it, drag its right edge to resize, or use the arrow keys for fine placement.',
        )}
      </Text>

      <Box
        ref={pageRef}
        onPointerDown={() => onSelect(null)}
        style={{
          position: 'relative',
          width: '100%',
          aspectRatio: String(aspectRatio),
          background: '#ffffff',
          border: '1px solid var(--mantine-color-gray-4)',
          overflow: 'hidden',
          // Blocks are positioned against this box, so it must not be static.
          touchAction: 'none',
        }}
      >
        {backgroundUrl && (
          <img
            src={backgroundUrl}
            alt=""
            style={{
              position: 'absolute',
              inset: 0,
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              pointerEvents: 'none',
            }}
          />
        )}

        {logoUrl && (
          <img
            src={logoUrl}
            alt=""
            style={{
              position: 'absolute',
              width: `${logoPlacement.widthPct ?? 18}%`,
              pointerEvents: 'none',
              ...logoStyle,
            }}
          />
        )}

        {placements.map((block) => {
          const isSelected = block.id === selectedId;
          const isImage = block.type === 'image';
          return (
            <div
              key={block.id}
              role="button"
              tabIndex={0}
              onPointerDown={(event) => startDrag(event, block, 'move')}
              onKeyDown={(event) => handleKeyDown(event, block)}
              style={{
                position: 'absolute',
                left: `${block.xPct}%`,
                top: `${block.yPct}%`,
                width: `${block.widthPct}%`,
                fontSize: block.fontSize ?? 14,
                fontWeight: block.fontWeight ?? 'normal',
                fontStyle: block.fontStyle ?? 'normal',
                textAlign: block.align ?? 'left',
                color: block.color ?? '#111111',
                cursor: disabled ? 'default' : 'move',
                outline: isSelected
                  ? '2px solid var(--mantine-color-blue-6)'
                  : '1px dashed var(--mantine-color-gray-5)',
                background: isSelected ? 'rgba(34,139,230,0.06)' : 'transparent',
                boxSizing: 'border-box',
                padding: 2,
                lineHeight: 1.3,
                wordWrap: 'break-word',
                userSelect: 'none',
                // An image block has no real image to show here — the actual
                // data URI is only resolved server-side at render/preview
                // time — so it gets a fixed square footprint and an icon
                // instead of stretching to a text block's shape.
                ...(isImage
                  ? {
                      aspectRatio: '1',
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center',
                      background: isSelected
                        ? 'rgba(34,139,230,0.08)'
                        : 'var(--mantine-color-gray-1)',
                    }
                  : {}),
              }}
            >
              {isImage ? (
                <IconPhoto size={18} color="var(--mantine-color-gray-6)" />
              ) : block.variable ? (
                `{{${block.variable}}}`
              ) : (
                block.text || ' '
              )}

              {isSelected && !disabled && (
                <span
                  onPointerDown={(event) => startDrag(event, block, 'resize')}
                  style={{
                    position: 'absolute',
                    right: -4,
                    top: '50%',
                    transform: 'translateY(-50%)',
                    width: 8,
                    height: 18,
                    borderRadius: 2,
                    background: 'var(--mantine-color-blue-6)',
                    cursor: 'ew-resize',
                  }}
                />
              )}
            </div>
          );
        })}
      </Box>
    </Paper>
  );
}
