import React from "react";
import {Audio} from "@remotion/media";
import {
  AbsoluteFill,
  Img,
  interpolate,
  Sequence,
  spring,
  staticFile,
  useCurrentFrame,
  useVideoConfig,
} from "remotion";
import {
  characters,
  initialVisibleCharacters,
  timeline,
  type AvatarDefinition,
  type CharacterId,
  type TimelineEvent,
} from "./data/script";
import {
  audioFileForSpeech,
  GAP_FRAMES,
  durationForTimelineEvent,
  hasAudioForSpeech,
} from "./data/timing";
import {roundedFontFamily} from "./fonts";

type ScheduledTimelineEvent = Readonly<{
  event: TimelineEvent;
  from: number;
  durationInFrames: number;
  visibleCharacters: CharacterId[];
  focusedCharacter: CharacterId;
}>;

const scheduleTimeline = (fps: number): ScheduledTimelineEvent[] => {
  let cursor = 0;
  const visibleCharacters = new Set<CharacterId>(initialVisibleCharacters);

  return timeline.map((event, index) => {
    visibleCharacters.add(event.character);

    const durationInFrames = durationForTimelineEvent(event, fps);
    const scheduledEvent = {
      event,
      from: cursor,
      durationInFrames,
      visibleCharacters: Array.from(visibleCharacters),
      focusedCharacter: event.character,
    };

    cursor += durationInFrames;
    if (index < timeline.length - 1) {
      cursor += GAP_FRAMES;
    }

    return scheduledEvent;
  });
};

const activeSegmentForFrame = (
  scheduledEvents: ScheduledTimelineEvent[],
  frame: number
) => {
  let activeSegment = scheduledEvents[0];

  for (const scheduledEvent of scheduledEvents) {
    if (frame >= scheduledEvent.from) {
      activeSegment = scheduledEvent;
    } else {
      break;
    }
  }

  return activeSegment;
};

const clampInterpolation = {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp",
} as const;

const Title: React.FC<Readonly<{progress: number}>> = ({progress}) => {
  const opacity = interpolate(progress, [0, 1], [0, 1], clampInterpolation);
  const translateY = interpolate(progress, [0, 1], [-30, 0], clampInterpolation);

  return (
    <div
      style={{
        fontFamily: roundedFontFamily,
        fontSize: 54,
        fontWeight: 700,
        color: "#1f2a44",
        letterSpacing: 1,
        textAlign: "center",
        marginTop: 40,
        opacity,
        transform: `translateY(${translateY}px)`,
        textShadow: "0 6px 18px rgba(31, 42, 68, 0.2)",
      }}
    >
      ネコミミはなぜかわいい？
    </div>
  );
};

const Subtitle: React.FC<
  Readonly<{
    text: string;
    progress: number;
    speakerName?: string;
    accentColor?: string;
  }>
> = ({text, progress, speakerName, accentColor = "#1f2a44"}) => {
  const opacity = interpolate(progress, [0, 1], [0, 1], clampInterpolation);
  const translateY = interpolate(progress, [0, 1], [16, 0], clampInterpolation);

  return (
    <div
      style={{
        fontFamily: roundedFontFamily,
        fontSize: 36,
        fontWeight: 700,
        color: "#1a1a1a",
        lineHeight: 1.4,
        padding: "18px 28px",
        backgroundColor: "rgba(255, 255, 255, 0.88)",
        borderRadius: 18,
        border: `2px solid ${accentColor}33`,
        boxShadow: "0 10px 30px rgba(31, 42, 68, 0.15)",
        maxWidth: 980,
        opacity,
        transform: `translateY(${translateY}px)`,
      }}
    >
      {speakerName ? (
        <div
          style={{
            display: "inline-block",
            fontSize: 20,
            color: "#ffffff",
            backgroundColor: accentColor,
            borderRadius: 999,
            padding: "4px 14px",
            marginBottom: 8,
          }}
        >
          {speakerName}
        </div>
      ) : null}
      <div>{text}</div>
    </div>
  );
};

const ZundamonAvatar: React.FC = () => {
  return (
    <div
      style={{
        width: 320,
        height: 320,
        borderRadius: "50%",
        background:
          "radial-gradient(circle at 30% 30%, #c9f6a5, #79d36f 70%)",
        border: "6px solid rgba(31, 42, 68, 0.1)",
        boxShadow: "0 18px 40px rgba(31, 42, 68, 0.2)",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        position: "relative",
      }}
    >
      <div
        style={{
          position: "absolute",
          top: -60,
          left: 40,
          width: 90,
          height: 90,
          background: "#79d36f",
          borderRadius: "10% 60% 20% 60%",
          transform: "rotate(-18deg)",
          border: "6px solid rgba(31, 42, 68, 0.1)",
        }}
      />
      <div
        style={{
          position: "absolute",
          top: -60,
          right: 40,
          width: 90,
          height: 90,
          background: "#79d36f",
          borderRadius: "60% 10% 60% 20%",
          transform: "rotate(18deg)",
          border: "6px solid rgba(31, 42, 68, 0.1)",
        }}
      />
      <div
        style={{
          display: "flex",
          gap: 36,
        }}
      >
        <div
          style={{
            width: 36,
            height: 46,
            borderRadius: "50%",
            background: "#1f2a44",
          }}
        />
        <div
          style={{
            width: 36,
            height: 46,
            borderRadius: "50%",
            background: "#1f2a44",
          }}
        />
      </div>
      <div
        style={{
          position: "absolute",
          bottom: 68,
          width: 90,
          height: 38,
          borderRadius: "0 0 80px 80px",
          borderBottom: "8px solid #1f2a44",
        }}
      />
    </div>
  );
};

const SayoAvatar: React.FC<Readonly<{accentColor: string}>> = ({
  accentColor,
}) => {
  return (
    <div
      style={{
        width: 320,
        height: 340,
        position: "relative",
      }}
    >
      <div
        style={{
          position: "absolute",
          left: 58,
          top: 10,
          width: 0,
          height: 0,
          borderLeft: "42px solid transparent",
          borderRight: "34px solid transparent",
          borderBottom: `112px solid ${accentColor}`,
          transform: "rotate(-22deg)",
          filter: "drop-shadow(0 8px 14px rgba(31, 42, 68, 0.18))",
        }}
      />
      <div
        style={{
          position: "absolute",
          right: 58,
          top: 10,
          width: 0,
          height: 0,
          borderLeft: "34px solid transparent",
          borderRight: "42px solid transparent",
          borderBottom: `112px solid ${accentColor}`,
          transform: "rotate(22deg)",
          filter: "drop-shadow(0 8px 14px rgba(31, 42, 68, 0.18))",
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 78,
          top: 42,
          width: 0,
          height: 0,
          borderLeft: "24px solid transparent",
          borderRight: "20px solid transparent",
          borderBottom: "66px solid #f0bfd4",
          transform: "rotate(-22deg)",
        }}
      />
      <div
        style={{
          position: "absolute",
          right: 78,
          top: 42,
          width: 0,
          height: 0,
          borderLeft: "20px solid transparent",
          borderRight: "24px solid transparent",
          borderBottom: "66px solid #f0bfd4",
          transform: "rotate(22deg)",
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 54,
          top: 72,
          width: 212,
          height: 218,
          borderRadius: "46% 46% 48% 48%",
          background: "#2f2f42",
          boxShadow: "0 18px 40px rgba(31, 42, 68, 0.22)",
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 80,
          top: 100,
          width: 160,
          height: 160,
          borderRadius: "48%",
          background: "radial-gradient(circle at 36% 28%, #fff4ee, #f5c6b9)",
          border: "6px solid rgba(31, 42, 68, 0.1)",
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 108,
          top: 150,
          display: "flex",
          gap: 44,
        }}
      >
        <div
          style={{
            width: 24,
            height: 34,
            borderRadius: "50%",
            background: "#222236",
          }}
        />
        <div
          style={{
            width: 24,
            height: 34,
            borderRadius: "50%",
            background: "#222236",
          }}
        />
      </div>
      <div
        style={{
          position: "absolute",
          left: 143,
          top: 194,
          width: 34,
          height: 18,
          borderRadius: "0 0 36px 36px",
          borderBottom: "5px solid #222236",
        }}
      />
      <div
        style={{
          position: "absolute",
          left: 96,
          top: 254,
          width: 128,
          height: 74,
          borderRadius: "46px 46px 20px 20px",
          background: `linear-gradient(160deg, ${accentColor}, #4b4560)`,
          border: "6px solid rgba(31, 42, 68, 0.1)",
        }}
      />
    </div>
  );
};

const CharacterAvatar: React.FC<
  Readonly<{
    characterId: CharacterId;
    focused: boolean;
    hasMultipleCharacters: boolean;
    bounce: number;
  }>
> = ({characterId, focused, hasMultipleCharacters, bounce}) => {
  const character = characters[characterId];
  const {avatar}: {avatar: AvatarDefinition} = character;
  const scale = hasMultipleCharacters ? 0.88 : focused ? 1.05 : 1;
  const opacity = focused || !hasMultipleCharacters ? 1 : 0.72;
  const translateY = focused ? bounce : bounce * 0.35;
  const imageTranslateY = avatar.imageLayout?.translateY ?? 0;
  const imageScaleX = avatar.imageLayout?.flipX ? -1 : 1;
  const imageTransform = `translateY(${imageTranslateY}px) scaleX(${imageScaleX})`;
  const nameplatePosition = avatar.nameplatePosition ?? "bottom";
  const showNameplate = nameplatePosition !== "none";

  const nameplate = (
    <div
      style={{
        position: "relative",
        zIndex: 2,
        fontFamily: roundedFontFamily,
        fontSize: 24,
        fontWeight: 700,
        color: "#ffffff",
        backgroundColor: avatar.accentColor,
        padding: "6px 18px",
        borderRadius: 999,
        boxShadow: "0 8px 20px rgba(31, 42, 68, 0.14)",
      }}
    >
      {character.displayName}
    </div>
  );

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        gap: 16,
        opacity,
        position: "relative",
        transform: `translateY(${translateY}px) scale(${scale})`,
      }}
    >
      {showNameplate && nameplatePosition === "top" ? nameplate : null}
      {avatar.imagePath ? (
        <Img
          src={staticFile(avatar.imagePath)}
          style={{
            width: avatar.imageLayout?.width ?? 320,
            maxHeight: avatar.imageLayout?.maxHeight ?? 360,
            objectFit: "contain",
            filter: "drop-shadow(0 18px 40px rgba(31, 42, 68, 0.22))",
            position: "relative",
            zIndex: 1,
            transform: imageTransform,
          }}
        />
      ) : avatar.kind === "sayo" ? (
        <div
          style={{position: "relative", zIndex: 1, transform: imageTransform}}
        >
          <SayoAvatar accentColor={avatar.accentColor} />
        </div>
      ) : (
        <div
          style={{position: "relative", zIndex: 1, transform: imageTransform}}
        >
          <ZundamonAvatar />
        </div>
      )}
      {showNameplate && nameplatePosition === "bottom" ? nameplate : null}
    </div>
  );
};

const Stage: React.FC<
  Readonly<{
    visibleCharacters: CharacterId[];
    focusedCharacter?: CharacterId;
    bounce: number;
  }>
> = ({visibleCharacters, focusedCharacter, bounce}) => {
  const hasMultipleCharacters = visibleCharacters.length > 1;

  return (
    <div
      style={{
        flex: 1,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        gap: hasMultipleCharacters ? 130 : 0,
        width: "100%",
        paddingBottom: 40,
      }}
    >
      {visibleCharacters.map((characterId) => (
        <CharacterAvatar
          key={characterId}
          characterId={characterId}
          focused={focusedCharacter === characterId}
          hasMultipleCharacters={hasMultipleCharacters}
          bounce={bounce}
        />
      ))}
    </div>
  );
};

const TimelineOverlay: React.FC<Readonly<{event: TimelineEvent}>> = ({event}) => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();

  const subtitleProgress = spring({
    frame,
    fps,
    config: {damping: 20, mass: 0.7},
  });

  const text = event.type === "say" ? event.subtitle ?? event.text : event.caption;
  if (!text) {
    return event.type === "say" && hasAudioForSpeech(event) ? (
      <Audio src={staticFile(audioFileForSpeech(event))} />
    ) : null;
  }

  const speech = event.type === "say" ? event : undefined;
  const character = speech ? characters[speech.character] : undefined;

  return (
    <>
      <div
        style={{
          position: "absolute",
          bottom: 40,
          left: 0,
          right: 0,
          display: "flex",
          justifyContent: "center",
        }}
      >
        <Subtitle
          text={text}
          progress={subtitleProgress}
          speakerName={character?.displayName}
          accentColor={character?.avatar.accentColor}
        />
      </div>
      {speech && hasAudioForSpeech(speech) ? (
        <Audio src={staticFile(audioFileForSpeech(speech))} />
      ) : null}
    </>
  );
};

const keyForEvent = (event: TimelineEvent, index: number) => {
  if (event.type === "say") {
    return event.id;
  }

  return `show-${event.character}-${index}`;
};

export const YukkuriComposition: React.FC = () => {
  const frame = useCurrentFrame();
  const {fps} = useVideoConfig();
  const scheduledEvents = scheduleTimeline(fps);
  const activeSegment = activeSegmentForFrame(scheduledEvents, frame);
  const isInsideActiveSegment =
    frame < activeSegment.from + activeSegment.durationInFrames;

  const titleProgress = spring({
    frame,
    fps,
    config: {damping: 18, mass: 0.6},
  });

  const bounce = interpolate(
    Math.sin((frame / fps) * Math.PI * 2),
    [-1, 1],
    [-10, 10]
  );

  const sequences = scheduledEvents.map((scheduledEvent, index) => (
    <Sequence
      key={keyForEvent(scheduledEvent.event, index)}
      from={scheduledEvent.from}
      durationInFrames={scheduledEvent.durationInFrames}
      premountFor={Math.min(fps, scheduledEvent.from)}
    >
      <TimelineOverlay event={scheduledEvent.event} />
    </Sequence>
  ));

  return (
    <AbsoluteFill
      style={{
        background:
          "radial-gradient(circle at top, #ffe8c7 0%, #ffd3b4 45%, #ffb6b6 100%)",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
      }}
    >
      <div
        style={{
          position: "absolute",
          inset: 0,
          backgroundImage:
            "radial-gradient(circle at 20% 20%, rgba(255,255,255,0.6) 0, rgba(255,255,255,0) 40%), radial-gradient(circle at 80% 30%, rgba(255,255,255,0.5) 0, rgba(255,255,255,0) 45%), radial-gradient(circle at 30% 80%, rgba(255,255,255,0.4) 0, rgba(255,255,255,0) 50%)",
          opacity: 0.8,
        }}
      />
      <Title progress={titleProgress} />
      <Stage
        visibleCharacters={activeSegment.visibleCharacters}
        focusedCharacter={
          isInsideActiveSegment ? activeSegment.focusedCharacter : undefined
        }
        bounce={bounce}
      />
      {sequences}
    </AbsoluteFill>
  );
};
