import React, {
  ComponentProps,
  CSSProperties,
  MouseEventHandler,
  ReactNode,
  useCallback,
  useMemo,
  useRef,
} from 'react';
import { Attachment } from './attachment';
import { isStationeryTheme, useTheme } from '../../hooks/useTheme';

type Corner = 'tl' | 'tr' | 'bl' | 'br';

type StationeryMediaProps = ComponentProps<typeof Attachment> & {
  children: ReactNode;
  /** Stable id (e.g. mxc URL) so tilt/tape don't reshuffle on timeline re-renders */
  tiltSeed?: string;
  /** Invisible bottom spacer (px) so following text stays on the ruled grid */
  gridPad?: number;
};

const CORNERS: { id: Corner; x: 0 | 1; y: 0 | 1 }[] = [
  { id: 'tl', x: 0, y: 0 },
  { id: 'tr', x: 1, y: 0 },
  { id: 'bl', x: 0, y: 1 },
  { id: 'br', x: 1, y: 1 },
];

const MEDIA_ROTS = [-0.9, 0.7, -0.5, 1.0, -1.1, 0.8, -0.4, 0.9] as const;

export function hashStationerySeed(seed: string): number {
  let h = 2166136261;
  for (let i = 0; i < seed.length; i += 1) {
    h ^= seed.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}

export function stationeryMediaRot(seed: string): string {
  return `${MEDIA_ROTS[hashStationerySeed(seed) % MEDIA_ROTS.length]}deg`;
}

const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));

/**
 * Fold sits halfway between corner and cursor; flap hinges on that fold.
 * `keep` = remaining photo; `flap` = lifted triangle.
 */
function cornerPeelPaths(
  id: Corner,
  w: number,
  h: number,
  cx: number,
  cy: number,
  ux: number,
  uy: number,
  size: number
): {
  keep: string;
  flap: string;
  origin: string;
  axisX: number;
  axisY: number;
  hingeSign: number;
} {
  const eps = 0.0001;
  // Fold line through the midpoint on corner→cursor, ⊥ to that ray
  const px = cx + ux * size;
  const py = cy + uy * size;

  let ax = 0;
  let ay = 0;
  let bx = 0;
  let by = 0;

  if (id === 'tr') {
    ax = clamp(Math.abs(ux) > eps ? px + (py * uy) / ux : px, 0, w);
    ay = 0;
    bx = w;
    by = clamp(Math.abs(uy) > eps ? py - ((w - px) * ux) / uy : py, 0, h);
  } else if (id === 'tl') {
    ax = clamp(Math.abs(ux) > eps ? px + (py * uy) / ux : px, 0, w);
    ay = 0;
    bx = 0;
    by = clamp(Math.abs(uy) > eps ? py - ((0 - px) * ux) / uy : py, 0, h);
  } else if (id === 'br') {
    ax = clamp(Math.abs(ux) > eps ? px + ((py - h) * uy) / ux : px, 0, w);
    ay = h;
    bx = w;
    by = clamp(Math.abs(uy) > eps ? py - ((w - px) * ux) / uy : py, 0, h);
  } else {
    ax = clamp(Math.abs(ux) > eps ? px + ((py - h) * uy) / ux : px, 0, w);
    ay = h;
    bx = 0;
    by = clamp(Math.abs(uy) > eps ? py - ((0 - px) * ux) / uy : py, 0, h);
  }

  let keep: string;
  if (id === 'tr') {
    keep = `polygon(0 0, ${ax}px 0, ${w}px ${by}px, ${w}px ${h}px, 0 ${h}px)`;
  } else if (id === 'tl') {
    keep = `polygon(${ax}px 0, ${w}px 0, ${w}px ${h}px, 0 ${h}px, 0 ${by}px)`;
  } else if (id === 'br') {
    keep = `polygon(0 0, ${w}px 0, ${w}px ${by}px, ${ax}px ${h}px, 0 ${h}px)`;
  } else {
    keep = `polygon(0 0, ${w}px 0, ${w}px ${h}px, ${ax}px ${h}px, 0 ${by}px)`;
  }

  const flap = `polygon(${cx}px ${cy}px, ${ax}px ${ay}px, ${bx}px ${by}px)`;

  // Hinge on the fold (midpoint), axis along the fold edge
  const midX = (ax + bx) / 2;
  const midY = (ay + by) / 2;
  const fdx = bx - ax;
  const fdy = by - ay;
  const flen = Math.hypot(fdx, fdy) || 1;
  const axisX = fdx / flen;
  const axisY = fdy / flen;
  // Sign so the corner swings up over the fold (right-hand rule)
  const cross = fdx * (cy - midY) - fdy * (cx - midX);
  const hingeSign = cross >= 0 ? 1 : -1;

  return {
    keep,
    flap,
    origin: `${midX}px ${midY}px`,
    axisX,
    axisY,
    hingeSign,
  };
}

/**
 * Stationery-theme image chrome: clear corner tape + a mouse-driven peel
 * that shows a duplicated slice of the photo lifting toward the cursor.
 */
export function StationeryMedia({
  children,
  tiltSeed = 'stationery',
  gridPad = 0,
  style,
  ...attachmentProps
}: StationeryMediaProps) {
  const theme = useTheme();
  const isStationery = isStationeryTheme(theme);
  const peelRef = useRef<HTMLSpanElement>(null);

  const tiltStyle = useMemo((): CSSProperties => {
    return {
      ...(style as CSSProperties | undefined),
      // Margin sits outside the paper chrome — snaps the grid without a white bar
      ...(isStationery && gridPad > 0 ? { marginBottom: `${gridPad}px` } : null),
      ['--media-rot' as string]: stationeryMediaRot(tiltSeed),
    };
  }, [tiltSeed, style, isStationery, gridPad]);

  const tapeAlt = useMemo(() => hashStationerySeed(tiltSeed) % 2 === 1, [tiltSeed]);
  // Default tape covers TL; alt covers TR — peel only the free top corner
  const peelCorner = useMemo(
    () => (tapeAlt ? CORNERS.find((c) => c.id === 'tl')! : CORNERS.find((c) => c.id === 'tr')!),
    [tapeAlt]
  );

  const clearPeel = useCallback((host: HTMLElement) => {
    const peel = peelRef.current;
    const target = host.querySelector('[data-paarrot-media-height]') as HTMLElement | null;
    if (peel) {
      // Hide flap first (no opacity tween) so restoring the photo can't double-draw
      peel.style.transition = 'none';
      peel.style.opacity = '0';
      peel.style.setProperty('--peel-hinge', '0deg');
      peel.style.clipPath = '';
    }
    if (target) target.style.clipPath = '';
  }, []);

  const syncPeel = useCallback(
    (host: HTMLElement, clientX: number, clientY: number) => {
      const peel = peelRef.current;
      if (!peel) return;

      const rect = host.getBoundingClientRect();
      const x = clientX - rect.left;
      const y = clientY - rect.top;
      const w = rect.width;
      const h = rect.height;

      const best = peelCorner;
      const cx = best.x * w;
      const cy = best.y * h;
      const dist = Math.hypot(cx - x, cy - y);
      const maxReach = Math.min(w, h) * 0.55;

      // Fold halfway between corner and cursor; fade when leaving the corner zone
      let size = 0;
      let amount = 0;
      if (dist > 8 && dist <= maxReach) {
        size = dist * 0.5;
        amount = Math.min(1, size / (Math.min(w, h) * 0.22));
      } else if (dist > maxReach && dist < maxReach * 1.35) {
        const fade = 1 - (dist - maxReach) / (maxReach * 0.35);
        size = maxReach * 0.5 * Math.max(0, fade);
        amount = Math.max(0, fade);
      }

      const img = host.querySelector('img');
      const url = img?.currentSrc || img?.src || '';

      // Unit vector from corner → cursor (triangle aims this way)
      let ux = x - cx;
      let uy = y - cy;
      if (best.id === 'tr') {
        ux = Math.min(-0.05, ux);
        uy = Math.max(0.05, uy);
      } else {
        ux = Math.max(0.05, ux);
        uy = Math.max(0.05, uy);
      }
      const len = Math.hypot(ux, uy) || 1;
      ux /= len;
      uy /= len;

      peel.dataset.corner = best.id;
      if (url) {
        peel.style.setProperty('--peel-img', `url(${JSON.stringify(url)})`);
      }

      const target = host.querySelector('[data-paarrot-media-height]') as HTMLElement | null;
      if (target && amount > 0.06 && size > 6) {
        const { keep, flap, origin, axisX, axisY, hingeSign } = cornerPeelPaths(
          best.id,
          w,
          h,
          cx,
          cy,
          ux,
          uy,
          size
        );
        target.style.clipPath = keep;
        peel.style.clipPath = flap;
        peel.style.transformOrigin = origin;
        peel.style.setProperty('--peel-axis-x', String(axisX));
        peel.style.setProperty('--peel-axis-y', String(axisY));
        peel.style.setProperty('--peel-hinge', `${hingeSign * amount * 52}deg`);
        peel.style.setProperty('--peel-tip-x', `${cx}px`);
        peel.style.setProperty('--peel-tip-y', `${cy}px`);
        peel.style.setProperty('--peel-grad-r', `${Math.max(size * 1.15, 12)}px`);
        peel.style.opacity = '1';
      } else {
        clearPeel(host);
      }
    },
    [peelCorner, clearPeel]
  );

  const handleMove: MouseEventHandler<HTMLElement> = (evt) => {
    syncPeel(evt.currentTarget, evt.clientX, evt.clientY);
  };

  const handleEnter: MouseEventHandler<HTMLElement> = (evt) => {
    syncPeel(evt.currentTarget, evt.clientX, evt.clientY);
  };

  const handleLeave: MouseEventHandler<HTMLElement> = (evt) => {
    clearPeel(evt.currentTarget);
  };

  if (!isStationery) {
    return (
      <Attachment style={style} {...attachmentProps}>
        {children}
      </Attachment>
    );
  }

  return (
    <Attachment
      {...attachmentProps}
      style={tiltStyle}
      data-stationery-media=""
      data-tape-alt={tapeAlt ? '' : undefined}
      onMouseEnter={handleEnter}
      onMouseMove={handleMove}
      onMouseLeave={handleLeave}
    >
      <span ref={peelRef} data-stationery-peel="" aria-hidden="true" />
      {children}
    </Attachment>
  );
}
