"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { useDebouncedValue } from "@/hooks/useDebouncedValue";
import {
  isDevelopmentEnv,
  loadLocalProject,
  saveLocalProject,
} from "@/lib/dev-project-storage";
import {
  contentValuesFromSceneData,
  fetchProject,
  isProjectNotFoundError,
  patchProjectContents,
  patchProjectSceneData,
} from "@/lib/project-api";
import { isDevProjectId } from "@/lib/project-ids";
import {
  PROJECT_SAVE_DEBOUNCE_MS,
  PROJECT_SAVED_DISPLAY_MS,
  type ProjectSaveStatus,
} from "@/lib/project-save-status";
import { isVideoSceneDataEmpty } from "@/lib/studio-scene-data";
import { useStudioStore } from "@/lib/studio-store";

export interface UseStudioProjectPersistenceResult {
  projectName: string;
  setProjectName: (name: string) => void;
  saveStatus: ProjectSaveStatus;
  usingLocalStorage: boolean;
  retrySave: () => void;
}

function applyLocalSnapshot(
  projectId: string,
  hydrateFromSceneData: (sceneData: Record<string, unknown>) => boolean,
  setProjectName: (name: string) => void
): string | null {
  const local = loadLocalProject(projectId);
  if (!local) return null;

  if (local.name) {
    setProjectName(local.name);
  }

  if (!isVideoSceneDataEmpty(local.scene_data)) {
    hydrateFromSceneData(local.scene_data);
  }

  return JSON.stringify(local.scene_data);
}

export function useStudioProjectPersistence(
  projectId: string
): UseStudioProjectPersistenceResult {
  const scenes = useStudioStore((state) => state.scenes);
  const activeSceneId = useStudioStore((state) => state.activeSceneId);
  const currentTime = useStudioStore((state) => state.currentTime);
  const pxPerSecond = useStudioStore((state) => state.pxPerSecond);
  const audioFileName = useStudioStore((state) => state.audioFileName);
  const audioSrc = useStudioStore((state) => state.audioSrc);
  const audioVolume = useStudioStore((state) => state.audioVolume);
  const sceneBackgroundColor = useStudioStore(
    (state) => state.sceneBackgroundColor
  );
  const sceneAccentColor = useStudioStore((state) => state.sceneAccentColor);
  const hydrateFromSceneData = useStudioStore(
    (state) => state.hydrateFromSceneData
  );
  const getSceneDataForSave = useStudioStore((state) => state.getSceneDataForSave);

  const [projectName, setProjectName] = useState(
    () => `Project ${projectId.slice(0, 8)}`
  );
  const [saveStatus, setSaveStatus] = useState<ProjectSaveStatus>("idle");
  const [usingLocalStorage, setUsingLocalStorage] = useState(false);

  const skipSaveRef = useRef(true);
  const lastSavedPayloadRef = useRef<string | null>(null);
  const savedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const projectNameRef = useRef(projectName);

  useEffect(() => {
    projectNameRef.current = projectName;
  }, [projectName]);

  const persistPayload = useMemo(
    () => JSON.stringify(getSceneDataForSave()),
    [
      scenes,
      activeSceneId,
      currentTime,
      pxPerSecond,
      audioFileName,
      audioSrc,
      audioVolume,
      sceneBackgroundColor,
      sceneAccentColor,
      getSceneDataForSave,
    ]
  );

  const debouncedPayload = useDebouncedValue(
    persistPayload,
    PROJECT_SAVE_DEBOUNCE_MS
  );

  const clearSavedTimer = useCallback(() => {
    if (savedTimerRef.current) {
      clearTimeout(savedTimerRef.current);
      savedTimerRef.current = null;
    }
  }, []);

  const markSaved = useCallback(
    (payloadJson: string, local: boolean) => {
      lastSavedPayloadRef.current = payloadJson;
      setSaveStatus(local ? "local" : "saved");
      clearSavedTimer();
      savedTimerRef.current = setTimeout(() => {
        setSaveStatus((current) =>
          current === "saved" || current === "local" ? "idle" : current
        );
      }, PROJECT_SAVED_DISPLAY_MS);
    },
    [clearSavedTimer]
  );

  const saveToLocalStorage = useCallback(
    (payloadJson: string) => {
      const scene_data = JSON.parse(payloadJson) as Record<string, unknown>;
      saveLocalProject(projectId, {
        scene_data,
        name: projectNameRef.current,
      });
      setUsingLocalStorage(true);
      lastSavedPayloadRef.current = payloadJson;
      setSaveStatus("local");
      clearSavedTimer();
      savedTimerRef.current = setTimeout(() => {
        setSaveStatus((current) => (current === "local" ? "idle" : current));
      }, PROJECT_SAVED_DISPLAY_MS);
    },
    [projectId, clearSavedTimer]
  );

  const performSave = useCallback(
    async (payloadJson: string) => {
      if (isDevProjectId(projectId)) return;

      setSaveStatus("saving");
      try {
        const scene_data = JSON.parse(payloadJson) as Record<string, unknown>;
        await patchProjectSceneData(projectId, scene_data);
        // Persist the per-input values to content elements so the render binder uses
        // them (best-effort — never block the main save on it).
        void patchProjectContents(
          projectId,
          contentValuesFromSceneData(scene_data)
        ).catch(() => {});
        setUsingLocalStorage(false);
        markSaved(payloadJson, false);
      } catch (error) {
        if (isDevelopmentEnv() && isProjectNotFoundError(error)) {
          saveToLocalStorage(payloadJson);
          return;
        }
        setSaveStatus("error");
      }
    },
    [projectId, markSaved, saveToLocalStorage]
  );

  const retrySave = useCallback(() => {
    void performSave(persistPayload);
  }, [performSave, persistPayload]);

  useEffect(() => {
    if (isDevProjectId(projectId)) return;

    let cancelled = false;
    skipSaveRef.current = true;

    void (async () => {
      try {
        const { project } = await fetchProject(projectId);
        if (cancelled) return;

        setUsingLocalStorage(false);
        setProjectName(project.name);

        if (!isVideoSceneDataEmpty(project.scene_data)) {
          hydrateFromSceneData(project.scene_data);
        }

        lastSavedPayloadRef.current = JSON.stringify(
          useStudioStore.getState().getSceneDataForSave()
        );
      } catch (error) {
        if (cancelled) return;

        if (isDevelopmentEnv() && isProjectNotFoundError(error)) {
          const savedPayload = applyLocalSnapshot(
            projectId,
            hydrateFromSceneData,
            setProjectName
          );
          if (savedPayload) {
            setUsingLocalStorage(true);
            lastSavedPayloadRef.current = JSON.stringify(
              useStudioStore.getState().getSceneDataForSave()
            );
          }
        } else {
          setSaveStatus("error");
        }
      } finally {
        if (!cancelled) {
          skipSaveRef.current = false;
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [projectId, hydrateFromSceneData]);

  useEffect(() => {
    if (isDevProjectId(projectId) || skipSaveRef.current) return;
    if (persistPayload === lastSavedPayloadRef.current) return;
    setSaveStatus("pending");
  }, [projectId, persistPayload]);

  useEffect(() => {
    if (isDevProjectId(projectId) || skipSaveRef.current) return;
    if (debouncedPayload === lastSavedPayloadRef.current) return;
    void performSave(debouncedPayload);
  }, [projectId, debouncedPayload, performSave]);

  useEffect(() => clearSavedTimer, [clearSavedTimer]);

  return {
    projectName,
    setProjectName,
    saveStatus,
    usingLocalStorage,
    retrySave,
  };
}
