// src/components/AsyncReveal.tsx
import React, { ReactNode } from "react";
import { AnimatePresence, motion, type Variants } from "framer-motion";
import styled, { keyframes } from "styled-components";
import { ClipLoader } from "react-spinners";

type AsyncRevealProps = {
  loading: boolean;
  error?: Error | string | null;
  /** Контент, который нужно показать ПОСЛЕ загрузки */
  children: ReactNode;
  /** Кастомный лоадер (по умолчанию спиннер) */
  loader?: ReactNode;
  /** Кастомная вьюха ошибки */
  errorView?: ReactNode | ((err: Error | string) => ReactNode);
  /** Стаггер между детьми (сек) */
  stagger?: number;
  /** Задержка перед началом анимации детей (сек) */
  delay?: number;
  /** Длительность анимации каждого ребёнка (сек) */
  duration?: number;
  /** Смещение по оси Y в начале (px) */
  from?: number;
};

export function AsyncReveal({
  loading,
  error,
  children,
  loader,
  errorView,
  stagger = 0.06,
  delay = 0,
  duration = 0.5,
  from = 12,
}: AsyncRevealProps) {
  if (loading) {
    return (
      <Center>
        {loader ?? (
          <LoaderBox>
            <ClipLoader size={44} />
          </LoaderBox>
        )}
      </Center>
    );
  }

  if (error) {
    const content =
      typeof errorView === "function"
        ? errorView(error)
        : errorView ?? <DefaultError>Failed to load</DefaultError>;
    return <Center>{content}</Center>;
  }

  const items = React.Children.toArray(children);

  return (
    <AnimatePresence mode="wait">
      <MotionContents
        variants={containerVariants(stagger, delay)}
        initial="hidden"
        animate="show"
        exit="hidden"
      >
        {items.map((child, i) => (
          <motion.div key={i} variants={itemVariants(duration, from)}>
            {child}
          </motion.div>
        ))}
      </MotionContents>
    </AnimatePresence>
  );
}

/* ================== styled / variants ================== */

const Contents = styled.div`
  display: contents; /* чтобы не ломать вашу сетку/флекс */
`;
const MotionContents = motion(Contents);

const DefaultError = styled.div`
  font-size: 14px;
  color: #ffb4b4;
`;

const Center = styled.div`
  width: 100%;
  display: flex;
  justify-content: center;
`;

const shimmer = keyframes`
  0% { opacity: .6; }
  50% { opacity: .95; }
  100% { opacity: .6; }
`;

const LoaderBox = styled.div`
  display: flex;
  gap: 12px;
  align-items: center;
  animation: ${shimmer} 2s ease-in-out infinite;
`;

const containerVariants = (stagger: number, delay: number): Variants => ({
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: Math.max(stagger, 0),
      delayChildren: Math.max(delay, 0),
    },
  },
});

// используем кубическую безье (ease-out), чтобы угодить типам framer-motion
const itemVariants = (duration: number, from: number): Variants => ({
  hidden: { opacity: 0, y: from },
  show: {
    opacity: 1,
    y: 0,
    transition: { duration: Math.max(duration, 0.05), ease: [0.16, 1, 0.3, 1] },
  },
});

/* ================== Доп. скелетоны (на будущее) ================== */
export const SkeletonLine = styled.div<{ w?: string; h?: string }>`
  width: ${({ w }) => w ?? "100%"};
  height: ${({ h }) => h ?? "16px"};
  border-radius: 8px;
  background: #e9ecef;
  animation: ${shimmer} 2s ease-in-out infinite;
`;

export const SkeletonBlock = styled.div<{ w?: string; h?: string }>`
  width: ${({ w }) => w ?? "100%"};
  height: ${({ h }) => h ?? "120px"};
  border-radius: 12px;
  background: #e9ecef;
  animation: ${shimmer} 2s ease-in-out infinite;
`;
