/**
 * Flying-image animation from a source element to the cart icon in the header.
 *
 * The helper clones the source image, fixes it at the source's viewport
 * position, then uses a single Web Animations API transition that translates
 * the clone towards the cart icon while shrinking, fading, and rotating
 * slightly. At completion we pulse the cart icon and emit a DOM event so the
 * header badge can briefly highlight the update.
 *
 * Design notes:
 *   - Uses `position: fixed` + coordinates derived from getBoundingClientRect
 *     so the animation works regardless of scroll position or layout.
 *   - Falls back silently when the source image or cart icon can't be located
 *     (e.g. in tests or when the user triggers the action from a hidden view).
 *   - Deliberately skipped when `prefers-reduced-motion` is set.
 */

const CART_ICON_SELECTOR = "[data-cart-icon]";
const PULSE_CLASS = "cart-icon-pulse";
const CART_UPDATED_EVENT = "cart:flyto:arrived";

function prefersReducedMotion(): boolean {
  if (typeof window === "undefined" || !window.matchMedia) return false;
  try {
    return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  } catch {
    return false;
  }
}

export interface FlyToCartOptions {
  /** Source element to clone (usually an <img>). */
  source: Element | null;
  /** Optional image URL to use for the clone when the source isn't an <img>. */
  imageSrc?: string | null;
  /** Optional fallback rectangle to fly from when `source` is unavailable. */
  fromRect?: DOMRect | null;
  /** Animation duration in ms. Default 650. */
  duration?: number;
}

/**
 * Fly a cloned image/element from its current on-screen location to the cart
 * icon. Resolves after the animation finishes (or immediately on fallback).
 */
export function flyToCart(opts: FlyToCartOptions): Promise<void> {
  if (typeof document === "undefined") return Promise.resolve();
  if (prefersReducedMotion()) {
    pulseCartIcon();
    return Promise.resolve();
  }

  const cartIcon = document.querySelector(CART_ICON_SELECTOR) as HTMLElement | null;
  if (!cartIcon) return Promise.resolve();

  // Derive source rectangle — prefer explicit `fromRect`, then element.
  const sourceRect =
    opts.fromRect ??
    (opts.source instanceof Element ? opts.source.getBoundingClientRect() : null);
  if (!sourceRect || sourceRect.width === 0 || sourceRect.height === 0) {
    pulseCartIcon();
    return Promise.resolve();
  }

  // Resolve the image URL. When the source itself is an <img>, reuse its
  // currentSrc so the clone matches what the user is seeing.
  const imageSrc =
    opts.imageSrc ||
    (opts.source instanceof HTMLImageElement
      ? opts.source.currentSrc || opts.source.src
      : null);

  // The clone: if we have an image URL, use an <img>, otherwise clone the node.
  let clone: HTMLElement;
  if (imageSrc) {
    const img = document.createElement("img");
    img.src = imageSrc;
    img.alt = "";
    img.decoding = "async";
    clone = img;
  } else if (opts.source instanceof HTMLElement) {
    clone = opts.source.cloneNode(true) as HTMLElement;
  } else {
    pulseCartIcon();
    return Promise.resolve();
  }

  // Start geometry: anchor the clone over the source.
  const size = Math.min(sourceRect.width, sourceRect.height, 160);
  Object.assign(clone.style, {
    position: "fixed",
    left: `${sourceRect.left + sourceRect.width / 2 - size / 2}px`,
    top: `${sourceRect.top + sourceRect.height / 2 - size / 2}px`,
    width: `${size}px`,
    height: `${size}px`,
    objectFit: "cover",
    borderRadius: "16px",
    boxShadow:
      "0 18px 36px -10px rgba(11,110,62,0.45), 0 6px 14px -6px rgba(0,0,0,0.25)",
    zIndex: "9999",
    pointerEvents: "none",
    willChange: "transform, opacity",
    transformOrigin: "center center",
    transform: "translate3d(0, 0, 0) scale(1) rotate(0deg)",
    opacity: "0.98",
  } as CSSStyleDeclaration);

  document.body.appendChild(clone);

  // End geometry: the center of the cart icon.
  const targetRect = cartIcon.getBoundingClientRect();
  const dx =
    targetRect.left + targetRect.width / 2 - (sourceRect.left + sourceRect.width / 2);
  const dy =
    targetRect.top + targetRect.height / 2 - (sourceRect.top + sourceRect.height / 2);

  // Use a two-keyframe animation with a subtle arc via midpoint.
  const midX = dx * 0.65;
  // Arc height scales with distance so longer flights curve more.
  const distance = Math.hypot(dx, dy);
  const arcHeight = Math.min(120, Math.max(40, distance * 0.18));
  const midY = dy * 0.55 - arcHeight;

  const duration = opts.duration ?? 650;

  const anim = clone.animate(
    [
      {
        transform: "translate3d(0, 0, 0) scale(1) rotate(0deg)",
        opacity: 0.98,
        offset: 0,
      },
      {
        transform: `translate3d(${midX}px, ${midY}px, 0) scale(0.55) rotate(-12deg)`,
        opacity: 0.9,
        offset: 0.55,
      },
      {
        transform: `translate3d(${dx}px, ${dy}px, 0) scale(0.12) rotate(8deg)`,
        opacity: 0,
        offset: 1,
      },
    ],
    {
      duration,
      easing: "cubic-bezier(0.22, 1, 0.36, 1)",
      fill: "forwards",
    },
  );

  return new Promise((resolve) => {
    const finish = () => {
      try {
        clone.remove();
      } catch {
        /* noop */
      }
      pulseCartIcon();
      try {
        window.dispatchEvent(new CustomEvent(CART_UPDATED_EVENT));
      } catch {
        /* noop */
      }
      resolve();
    };
    anim.addEventListener("finish", finish, { once: true });
    anim.addEventListener("cancel", finish, { once: true });
    // Safety timeout in case neither event fires.
    window.setTimeout(finish, duration + 250);
  });
}

/** Briefly pulse the cart icon to emphasise the update. */
export function pulseCartIcon(): void {
  const cartIcon = document.querySelector(CART_ICON_SELECTOR) as HTMLElement | null;
  if (!cartIcon) return;
  // Restart the animation by toggling the class off/on.
  cartIcon.classList.remove(PULSE_CLASS);
  // Force a reflow so the re-added class restarts the keyframes.
  void cartIcon.offsetWidth;
  cartIcon.classList.add(PULSE_CLASS);
  window.setTimeout(() => cartIcon.classList.remove(PULSE_CLASS), 700);
}