import { useCallback, useMemo, useState } from "react";
import { z } from "zod";
import { CompositionProps } from "../../types/constants";
import { SSEMessage } from "../../types/schema";

export type State =
  | {
      status: "init";
    }
  | {
      status: "invoking";
      phase: string;
      progress: number;
      subtitle: string | null;
    }
  | {
      status: "error";
      error: Error;
    }
  | {
      url: string;
      size: number;
      status: "done";
    };

export const useRendering = (
  id: string,
  inputProps: z.infer<typeof CompositionProps>,
) => {
  const [state, setState] = useState<State>({
    status: "init",
  });

  const renderMedia = useCallback(async () => {
    setState({
      status: "invoking",
      phase: "Starting...",
      progress: 0,
      subtitle: null,
    });

    try {
      const response = await fetch("/api/render", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ id, inputProps }),
      });

      if (!response.ok || !response.body) {
        throw new Error("Failed to start render");
      }

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n\n");
        buffer = lines.pop() || "";

        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;

          const json = line.slice(6);
          const message = JSON.parse(json) as SSEMessage;

          switch (message.type) {
            case "phase":
              setState((prev) => {
                if (prev.status !== "invoking") return prev;
                return {
                  ...prev,
                  phase: message.phase,
                  progress: message.progress,
                  subtitle: message.subtitle ?? null,
                };
              });
              break;
            case "done":
              setState({
                status: "done",
                url: message.url,
                size: message.size,
              });
              break;
            case "error":
              setState({
                status: "error",
                error: new Error(message.message),
              });
              break;
            default:
              message satisfies never;
              break;
          }
        }
      }
    } catch (err) {
      setState({
        status: "error",
        error: err as Error,
      });
    }
  }, [id, inputProps]);

  const undo = useCallback(() => {
    setState({ status: "init" });
  }, []);

  return useMemo(() => {
    return {
      renderMedia,
      state,
      undo,
    };
  }, [renderMedia, state, undo]);
};
