import {
  defineVQChronologicalScenario,
  totalVQChronologicalScenarioDurationInFrames,
} from "../../lib/VQRemotionLib/scenario";
import {
  doesVQTimelineEventAdvanceTimeline,
  type VQVideoEvent,
} from "../../lib/VQRemotionLib";
import {timeline, type SpeechEvent, type TimelineEvent} from "./script";
import voicevoxManifest from "./voicevox-manifest.json";

type ManifestEntry = {
  id: string;
  character?: string;
  speakerName?: string;
  styleName?: string;
  speakerId?: number;
  file: string;
  durationSeconds: number;
};

const manifestEntries = voicevoxManifest as ManifestEntry[];
const manifestById = new Map(
  manifestEntries.map((entry) => [entry.id, entry])
);

export const PIZZA_KILN_FPS = 30;
export const PIZZA_KILN_GAP_FRAMES = 6;

export const hasAudioForSpeech = (speech: SpeechEvent) =>
  manifestById.has(speech.id);

export const audioFileForSpeech = (speech: SpeechEvent) =>
  manifestById.get(speech.id)?.file ??
  `audio/pizza-kiln/lines/${speech.id}.wav`;

export const durationForSpeech = (
  speech: SpeechEvent,
  fps = PIZZA_KILN_FPS
) => {
  const entry = manifestById.get(speech.id);
  if (entry && Number.isFinite(entry.durationSeconds)) {
    return Math.max(1, Math.ceil(entry.durationSeconds * fps));
  }

  const estimatedSeconds = Math.max(1.2, speech.text.length * 0.11);
  return Math.ceil(estimatedSeconds * fps);
};

export const durationForVideo = (
  event: VQVideoEvent,
  fps = PIZZA_KILN_FPS
) => {
  if (event.durationFrames && Number.isFinite(event.durationFrames)) {
    return Math.max(1, Math.ceil(event.durationFrames));
  }

  if (event.durationSeconds && Number.isFinite(event.durationSeconds)) {
    return Math.max(1, Math.ceil(event.durationSeconds * fps));
  }

  if (event.trimAfterFrames && Number.isFinite(event.trimAfterFrames)) {
    return Math.max(
      1,
      Math.ceil(event.trimAfterFrames - (event.trimBeforeFrames ?? 0))
    );
  }

  throw new Error(`Video event "${event.id}" needs durationFrames or durationSeconds.`);
};

export const durationForTimelineEvent = (
  event: TimelineEvent,
  fps = PIZZA_KILN_FPS
) => {
  if (event.type === "say") {
    return durationForSpeech(event, fps);
  }

  if (event.type === "video") {
    return durationForVideo(event, fps);
  }

  if (event.durationSeconds && Number.isFinite(event.durationSeconds)) {
    return Math.max(1, Math.ceil(event.durationSeconds * fps));
  }

  throw new Error(`Timeline event "${event.type}" needs a duration.`);
};

export const pizzaKilnScenario = defineVQChronologicalScenario<TimelineEvent>({
  timeline,
  gapFrames: PIZZA_KILN_GAP_FRAMES,
  durationForEvent: durationForTimelineEvent,
  doesEventAdvanceTimeline: doesVQTimelineEventAdvanceTimeline,
});

export const totalPizzaKilnDurationInFrames = (fps = PIZZA_KILN_FPS) =>
  totalVQChronologicalScenarioDurationInFrames(pizzaKilnScenario, fps);
