import type { Signature } from '@prisma/client';
import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion';
import { useCallback, useEffect, useRef, useState } from 'react';
import { match, P } from 'ts-pattern';

import { cn } from '../lib/utils';
import { Card, CardContent } from '../primitives/card';

export type SigningCardProps = {
  className?: string;
  name: string;
  signature?: Pick<Signature, 'signatureImageAsBase64' | 'typedSignature'>;
  signingCelebrationImage?: string;
};

/**
 * 2D signing card.
 */
export const SigningCard = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
  return (
    <div className={cn('relative w-full max-w-sm md:max-w-md', className)}>
      <SigningCardContent name={name} signature={signature} />

      {signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
    </div>
  );
};

/**
 * 3D signing card that follows the mouse movement within a certain range.
 */
export const SigningCard3D = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
  // Should use % based dimensions by calculating the window height/width.
  const boundary = 400;

  const [trackMouse, setTrackMouse] = useState(false);

  const timeoutRef = useRef<number | undefined>(undefined);

  const cardX = useMotionValue(0);
  const cardY = useMotionValue(0);
  const rotateX = useTransform(cardY, [-600, 600], [8, -8]);
  const rotateY = useTransform(cardX, [-600, 600], [-8, 8]);

  const diagonalMovement = useTransform<number, number>(
    [rotateX, rotateY],
    ([newRotateX, newRotateY]) => newRotateX + newRotateY,
  );

  const sheenPosition = useTransform(diagonalMovement, [-16, 16], [-100, 200]);
  const sheenOpacity = useTransform(sheenPosition, [-100, 50, 200], [0, 0.1, 0]);
  const sheenGradient = useMotionTemplate`linear-gradient(
    30deg,
    transparent,
    rgba(var(--sheen-color) / ${sheenOpacity}) ${sheenPosition}%,
    transparent)`;

  const cardRef = useRef<HTMLDivElement>(null);

  const cardCenterPosition = useCallback(() => {
    if (!cardRef.current) {
      return { x: 0, y: 0 };
    }

    const { x, y, width, height } = cardRef.current.getBoundingClientRect();

    return { x: x + width / 2, y: y + height / 2 };
  }, [cardRef]);

  const onMouseMove = useCallback(
    (event: MouseEvent) => {
      const { x, y } = cardCenterPosition();

      const offsetX = event.clientX - x;
      const offsetY = event.clientY - y;

      // Calculate distance between the mouse pointer and center of the card.
      const distance = Math.sqrt(offsetX * offsetX + offsetY * offsetY);

      // Mouse enters enter boundary.
      if (distance <= boundary && !trackMouse) {
        setTrackMouse(true);
      } else if (!trackMouse) {
        return;
      }

      cardX.set(offsetX);
      cardY.set(offsetY);

      clearTimeout(timeoutRef.current);

      // Revert the card back to the center position after the mouse stops moving.
      timeoutRef.current = window.setTimeout(() => {
        void animate(cardX, 0, { duration: 2, ease: 'backInOut' });
        void animate(cardY, 0, { duration: 2, ease: 'backInOut' });

        void animate(sheenOpacity, 0, { duration: 2, ease: 'backInOut' });

        setTrackMouse(false);
      }, 1000);
    },
    [cardX, cardY, cardCenterPosition, trackMouse, sheenOpacity],
  );

  useEffect(() => {
    window.addEventListener('mousemove', onMouseMove);

    return () => {
      window.removeEventListener('mousemove', onMouseMove);
      if (timeoutRef.current) {
        window.clearTimeout(timeoutRef.current);
      }
    };
  }, [onMouseMove]);

  return (
    <div className={cn('relative w-full max-w-sm md:max-w-md', className)} style={{ perspective: 800 }}>
      <motion.div
        className="w-full rounded-lg bg-background [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
        ref={cardRef}
        style={{
          perspective: '800',
          backgroundImage: sheenGradient,
          transformStyle: 'preserve-3d',
          rotateX,
          rotateY,
        }}
      >
        <SigningCardContent className="bg-transparent" name={name} signature={signature} />
      </motion.div>

      {signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
    </div>
  );
};

type SigningCardContentProps = {
  name: string;
  signature?: Pick<Signature, 'signatureImageAsBase64' | 'typedSignature'>;
  className?: string;
};

const SigningCardContent = ({ className, name, signature }: SigningCardContentProps) => {
  return (
    <Card
      className={cn('group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center', className)}
      degrees={-145}
      gradient
    >
      <CardContent
        className="p-6 text-center font-signature"
        style={{
          container: 'main',
        }}
      >
        {match(signature)
          .with({ signatureImageAsBase64: P.string }, (signature) => (
            <img src={signature.signatureImageAsBase64} alt="signature" className="h-full max-w-[100%] dark:invert" />
          ))
          .with({ typedSignature: P.string }, (signature) => (
            <span
              className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
              style={{
                fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(4)}cqw), 1.875rem)`,
              }}
            >
              {signature.typedSignature}
            </span>
          ))
          .otherwise(() => (
            <span
              className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
              style={{
                fontSize: `max(min(4rem, ${(100 / name.length / 2).toFixed(4)}cqw), 1.875rem)`,
              }}
            >
              {name}
            </span>
          ))}
      </CardContent>
    </Card>
  );
};

type SigningCardImageProps = {
  signingCelebrationImage: string;
};

const SigningCardImage = ({ signingCelebrationImage }: SigningCardImageProps) => {
  return (
    <motion.div
      className="pointer-events-none absolute -inset-32 -z-50 flex items-center justify-center md:-inset-44 xl:-inset-60 2xl:-inset-80"
      initial={{
        opacity: 0,
        scale: 0.6,
      }}
      animate={{
        scale: 1,
        opacity: 0.6,
      }}
      transition={{
        delay: 0.5,
        duration: 0.5,
      }}
    >
      <img
        src={signingCelebrationImage}
        alt="background pattern"
        className="w-full dark:brightness-150 dark:contrast-[70%] dark:invert dark:sepia"
        style={{
          mask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',
          WebkitMask: 'radial-gradient(rgba(255, 255, 255, 1) 0%, transparent 67%)',
        }}
      />
    </motion.div>
  );
};
