import React, {Children, type CSSProperties, type ReactNode} from "react";
import {useCurrentFrame, useVideoConfig} from "remotion";
import {finite, lerp, nonNegative, phase, positive, reveal, staggerAt, swapState, type MotionTiming, type Point} from "./core.js";

export type MotionProps = MotionTiming & {children: ReactNode; style?: CSSProperties};

export function FadeIn({children, at = 0, duration = 0.6, style}: MotionProps) {
  const p = reveal(useCurrentFrame(), useVideoConfig().fps, at, duration);
  return <div style={{...style, opacity: p}}>{children}</div>;
}

export type SlideInProps = MotionProps & {from?: "up" | "down" | "left" | "right"; distance?: number; fade?: boolean};
export function SlideIn({children, at = 0, duration = 0.6, from = "up", distance = 40, fade = true, style}: SlideInProps) {
  const p = reveal(useCurrentFrame(), useVideoConfig().fps, at, duration);
  nonNegative(distance, "distance");
  if (!["up", "down", "left", "right"].includes(from)) throw new TypeError("from must be up, down, left, or right.");
  const axis = from === "left" || from === "right" ? "X" : "Y";
  const sign = from === "left" || from === "up" ? -1 : 1;
  return <div style={{...style, opacity: fade ? p : 1, transform: `translate${axis}(${sign * distance * (1 - p)}px)`}}>{children}</div>;
}

export type ScaleInProps = MotionProps & {from?: number; origin?: string};
export function ScaleIn({children, at = 0, duration = 0.6, from = 0.88, origin = "50% 50%", style}: ScaleInProps) {
  const p = reveal(useCurrentFrame(), useVideoConfig().fps, at, duration);
  nonNegative(from, "from");
  return <div style={{...style, opacity: p, transform: `scale(${lerp(from, 1, p)})`, transformOrigin: origin}}>{children}</div>;
}

export type AnchoredRevealProps = MotionProps & {anchor: Point; from?: number};
export function AnchoredReveal({children, anchor, from = 0.75, at = 0, duration = 0.6, style}: AnchoredRevealProps) {
  finite(anchor.x, "anchor.x"); finite(anchor.y, "anchor.y");
  return <ScaleIn at={at} duration={duration} from={from} origin={`${anchor.x}px ${anchor.y}px`} style={style}>{children}</ScaleIn>;
}

export type StaggerCascadeProps = MotionProps & {stagger?: number; distance?: number; itemStyle?: CSSProperties};
export function StaggerCascade({children, at = 0, duration = 0.55, stagger = 0.09, distance = 24, style, itemStyle}: StaggerCascadeProps) {
  nonNegative(stagger, "stagger"); nonNegative(distance, "distance"); positive(duration, "duration"); finite(at, "at");
  return <div style={style}>{Children.map(children, (child, i) => <SlideIn key={i} at={staggerAt(at, i, stagger)} duration={duration} from="down" distance={distance} style={itemStyle}>{child}</SlideIn>)}</div>;
}

export type ValueSwapProps = MotionTiming & {from: ReactNode; to: ReactNode; style?: CSSProperties};
/** Both values occupy the same grid cell, reserving the larger value's geometry. */
export function ValueSwap({from, to, at = 0, duration = 0.5, style}: ValueSwapProps) {
  const state = swapState(phase(useCurrentFrame(), useVideoConfig().fps, at, duration));
  return <span style={{display: "inline-grid", ...style}}>
    <span aria-hidden={state.side !== "from"} style={{gridArea: "1 / 1", opacity: state.side === "from" ? state.opacity : 0}}>{from}</span>
    <span aria-hidden={state.side !== "to"} style={{gridArea: "1 / 1", opacity: state.side === "to" ? state.opacity : 0}}>{to}</span>
  </span>;
}
