import { create } from "zustand";

import type {
  AddLayerInput,
  Layer,
  LayerProps,
  LayerType,
  Scene,
} from "@/lib/studio-types";
import {
  DEFAULT_LAYER_SIZE,
  DEFAULT_SCENE_DURATION,
} from "@/lib/studio-types";
import type { BrowserSceneItem } from "@/lib/scene-browser-data";
import {
  DEFAULT_PX_PER_SECOND,
  getNextZoomLevel,
  getProjectDuration,
  getSceneAtTime,
  getSceneStartTime,
} from "@/lib/studio-timeline";
import {
  captureHistorySnapshot,
  cloneScenes,
  STUDIO_HISTORY_LIMIT,
  type StudioHistorySnapshot,
} from "@/lib/studio-history";
import {
  contrastTextColor,
  DEFAULT_SCENE_ACCENT_COLOR,
  DEFAULT_SCENE_BACKGROUND_COLOR,
} from "@/lib/studio-color-palettes";
import {
  buildVideoSceneDataPayload,
  parseVideoSceneData,
} from "@/lib/studio-scene-data";
import type { SceneTransition } from "@/lib/studio-types";
import {
  captureSceneThumbnailFromStage,
  scheduleSceneThumbnailCapture,
} from "@/lib/studio-scene-thumbnail";
import { uuid } from "@/lib/uuid";

function createId(): string {
  return uuid();
}

function defaultPropsForType(type: LayerType): LayerProps {
  switch (type) {
    case "text":
      return {
        text: "Text",
        fontSize: 48,
        fill: "#111827",
        fontFamily: "Inter, sans-serif",
        align: "left",
        letterSpacing: 0,
        lineHeight: 1.2,
        animation: "none",
      };
    case "image":
      return { src: "", cornerRadius: 0 };
    case "video":
      return { src: "", fileName: "" };
    case "shape":
      return {
        shape: "rect",
        fill: "#2563EB",
        stroke: "#1E3A8A",
        strokeWidth: 0,
        cornerRadius: 0,
      };
    default:
      return {};
  }
}

function createDefaultScene(name: string): Scene {
  return {
    id: createId(),
    name,
    duration: DEFAULT_SCENE_DURATION,
    transitionType: "none",
    layers: [
      {
        id: createId(),
        type: "text",
        x: 240,
        y: 270,
        width: 800,
        height: 100,
        rotation: 0,
        opacity: 1,
        zIndex: 1,
        props: {
          text: "Your Title Here",
          fontSize: 72,
          fill: "#111827",
          fontFamily: "Inter, sans-serif",
          align: "center",
          bold: true,
          letterSpacing: 0,
          lineHeight: 1.2,
          animation: "none",
        },
      },
      {
        id: createId(),
        type: "text",
        x: 290,
        y: 390,
        width: 700,
        height: 60,
        rotation: 0,
        opacity: 1,
        zIndex: 2,
        props: {
          text: "Add your subtitle here",
          fontSize: 32,
          fill: "#6b7280",
          fontFamily: "Inter, sans-serif",
          align: "center",
          bold: false,
          letterSpacing: 0,
          lineHeight: 1.4,
          animation: "none",
        },
      },
    ],
  };
}

function createDefaultLayer(type: LayerType, zIndex: number): Layer {
  return {
    id: createId(),
    type,
    x: 100,
    y: 100,
    width: DEFAULT_LAYER_SIZE.width,
    height: DEFAULT_LAYER_SIZE.height,
    rotation: 0,
    opacity: 1,
    zIndex,
    props: defaultPropsForType(type),
  };
}

const initialScene = createDefaultScene("Scene 1");

export interface StudioState {
  scenes: Scene[];
  activeSceneId: string;
  selectedLayerId: string | null;
  isPlaying: boolean;
  currentTime: number;
  pxPerSecond: number;
  audioFileName: string | null;
  audioSrc: string | null;
  audioVolume: number;
  sceneBackgroundColor: string;
  sceneAccentColor: string;
  /** Project render mode (FIX / FLEXIBLE / MusicVisualizer / …). Empty until hydrated. */
  chooseMode: string;
  past: StudioHistorySnapshot[];
  future: StudioHistorySnapshot[];
  layerClipboard: Layer | null;
}

export interface StudioActions {
  addScene: (name?: string) => void;
  addSceneFromTemplate: (template: BrowserSceneItem) => void;
  deleteScene: (sceneId: string) => void;
  duplicateScene: (sceneId: string) => void;
  reorderScenes: (fromIndex: number, toIndex: number) => void;
  updateScene: (sceneId: string, updates: Partial<Scene>) => void;
  updateSceneThumbnail: (sceneId: string) => void;
  setActiveScene: (sceneId: string) => void;
  addLayer: (input: LayerType | AddLayerInput) => void;
  updateLayer: (layerId: string, updates: Partial<Layer>) => void;
  moveLayerToFront: (layerId: string) => void;
  moveLayerToBack: (layerId: string) => void;
  deleteLayer: (layerId: string) => void;
  copyLayer: (layerId: string) => void;
  pasteLayer: () => void;
  setSelectedLayer: (layerId: string | null) => void;
  undo: () => void;
  redo: () => void;
  togglePlay: () => void;
  startPlayback: () => void;
  stopPlayback: () => void;
  setCurrentTime: (time: number) => void;
  setPxPerSecond: (pxPerSecond: number) => void;
  timelineZoomIn: () => void;
  timelineZoomOut: () => void;
  setAudioTrack: (fileName: string, src: string) => void;
  clearAudioTrack: () => void;
  setAudioVolume: (volume: number) => void;
  setSceneBackgroundColor: (color: string) => void;
  setSceneAccentColor: (color: string) => void;
  applyPaletteToAllScenes: (mainColor: string, accentColor: string) => void;
  applyTransitionToAllScenes: (transitionType: SceneTransition) => void;
  applyFontFamilyToAllTextLayers: (fontFamily: string) => void;
  hydrateFromSceneData: (sceneData: Record<string, unknown>) => boolean;
  getSceneDataForSave: () => Record<string, unknown>;
}

export type StudioStore = StudioState & StudioActions;

function pushHistory(
  state: StudioState,
  next: Partial<StudioState>
): Partial<StudioState> {
  return {
    past: [...state.past, captureHistorySnapshot(state)].slice(
      -STUDIO_HISTORY_LIMIT
    ),
    future: [],
    ...next,
  };
}

function updateActiveSceneLayers(
  scenes: Scene[],
  activeSceneId: string,
  updater: (layers: Layer[]) => Layer[]
): Scene[] {
  return scenes.map((scene) =>
    scene.id !== activeSceneId
      ? scene
      : { ...scene, layers: updater(scene.layers) }
  );
}

export const useStudioStore = create<StudioStore>((set, get) => {
  const scheduleActiveSceneThumbnailUpdate = (): void => {
    const sceneId = get().activeSceneId;
    scheduleSceneThumbnailCapture(() => {
      get().updateSceneThumbnail(sceneId);
    });
  };

  return {
  scenes: [initialScene],
  activeSceneId: initialScene.id,
  selectedLayerId: null,
  isPlaying: false,
  currentTime: 0,
  pxPerSecond: DEFAULT_PX_PER_SECOND,
  audioFileName: null,
  audioSrc: null,
  audioVolume: 100,
  sceneBackgroundColor: DEFAULT_SCENE_BACKGROUND_COLOR,
  sceneAccentColor: DEFAULT_SCENE_ACCENT_COLOR,
  chooseMode: "",
  past: [],
  future: [],
  layerClipboard: null,

  addScene: (name) => {
    const scene = createDefaultScene(
      name ?? `Scene ${get().scenes.length + 1}`
    );
    set({
      scenes: [...get().scenes, scene],
      activeSceneId: scene.id,
      selectedLayerId: null,
      currentTime: 0,
      isPlaying: false,
    });
  },

  addSceneFromTemplate: (template) => {
    const layers: Layer[] = template.templateLayers.map((tl) => ({
      ...tl,
      id: createId(),
    }));
    const scene: Scene = {
      id: createId(),
      name: template.name,
      duration: DEFAULT_SCENE_DURATION,
      layers,
      transitionType: "none",
    };
    set({
      scenes: [...get().scenes, scene],
      activeSceneId: scene.id,
      selectedLayerId: null,
      currentTime: 0,
      isPlaying: false,
    });
  },

  deleteScene: (sceneId) => {
    const { scenes, activeSceneId } = get();
    if (scenes.length <= 1) return;

    const nextScenes = scenes.filter((scene) => scene.id !== sceneId);
    const nextActiveId =
      activeSceneId === sceneId
        ? (nextScenes[0]?.id ?? activeSceneId)
        : activeSceneId;

    set({
      scenes: nextScenes,
      activeSceneId: nextActiveId,
      selectedLayerId: null,
      currentTime: 0,
      isPlaying: false,
    });
  },

  duplicateScene: (sceneId) => {
    const { scenes } = get();
    const source = scenes.find((scene) => scene.id === sceneId);
    if (!source) return;

    const copy: Scene = {
      ...source,
      id: createId(),
      name: `${source.name} (copy)`,
      thumbnailUrl: source.thumbnailUrl,
      layers: source.layers.map((layer) => ({
        ...layer,
        id: createId(),
        props: { ...layer.props },
      })),
    };

    const index = scenes.findIndex((scene) => scene.id === sceneId);
    const nextScenes = [...scenes];
    nextScenes.splice(index + 1, 0, copy);

    set({
      scenes: nextScenes,
      activeSceneId: copy.id,
      selectedLayerId: null,
      currentTime: 0,
      isPlaying: false,
    });
  },

  reorderScenes: (fromIndex, toIndex) => {
    const scenes = [...get().scenes];
    const [removed] = scenes.splice(fromIndex, 1);
    if (!removed) return;
    scenes.splice(toIndex, 0, removed);
    set({ scenes });
  },

  updateScene: (sceneId, updates) => {
    set({
      scenes: get().scenes.map((scene) =>
        scene.id === sceneId ? { ...scene, ...updates } : scene
      ),
    });
  },

  updateSceneThumbnail: (sceneId) => {
    const state = get();
    if (state.activeSceneId !== sceneId) return;

    const thumbnailUrl = captureSceneThumbnailFromStage();
    if (!thumbnailUrl) return;

    set({
      scenes: state.scenes.map((scene) =>
        scene.id === sceneId ? { ...scene, thumbnailUrl } : scene
      ),
    });
  },

  setActiveScene: (sceneId) => {
    set({
      activeSceneId: sceneId,
      selectedLayerId: null,
      currentTime: getSceneStartTime(get().scenes, sceneId),
      isPlaying: false,
    });
  },

  addLayer: (input) => {
    const state = get();
    const sceneIndex = state.scenes.findIndex(
      (scene) => scene.id === state.activeSceneId
    );
    if (sceneIndex === -1) return;

    const config: AddLayerInput =
      typeof input === "string" ? { type: input } : input;

    const scene = state.scenes[sceneIndex];
    const maxZIndex = scene.layers.reduce(
      (max, layer) => Math.max(max, layer.zIndex),
      0
    );
    const base = createDefaultLayer(config.type, maxZIndex + 1);
    const layer: Layer = {
      ...base,
      x: config.x ?? base.x,
      y: config.y ?? base.y,
      width: config.width ?? base.width,
      height: config.height ?? base.height,
      rotation: config.rotation ?? base.rotation,
      opacity: config.opacity ?? base.opacity,
      props: { ...base.props, ...config.props },
    };

    const nextScenes = [...state.scenes];
    nextScenes[sceneIndex] = {
      ...scene,
      layers: [...scene.layers, layer],
    };

    set(
      pushHistory(state, {
        scenes: nextScenes,
        selectedLayerId: layer.id,
      })
    );
    scheduleActiveSceneThumbnailUpdate();
  },

  updateLayer: (layerId, updates) => {
    const state = get();
    set(
      pushHistory(state, {
        scenes: updateActiveSceneLayers(
          state.scenes,
          state.activeSceneId,
          (layers) =>
            layers.map((layer) =>
              layer.id === layerId ? { ...layer, ...updates } : layer
            )
        ),
      })
    );
    scheduleActiveSceneThumbnailUpdate();
  },

  moveLayerToFront: (layerId) => {
    const state = get();
    const scene = getActiveScene(state);
    if (!scene) return;

    const maxZIndex = scene.layers.reduce(
      (max, layer) => Math.max(max, layer.zIndex),
      0
    );
    const target = scene.layers.find((layer) => layer.id === layerId);
    if (!target || target.zIndex >= maxZIndex) return;

    set(
      pushHistory(state, {
        scenes: updateActiveSceneLayers(
          state.scenes,
          state.activeSceneId,
          (layers) =>
            layers.map((layer) =>
              layer.id === layerId
                ? { ...layer, zIndex: maxZIndex + 1 }
                : layer
            )
        ),
      })
    );
  },

  moveLayerToBack: (layerId) => {
    const state = get();
    const scene = getActiveScene(state);
    if (!scene) return;

    const minZIndex = scene.layers.reduce(
      (min, layer) => Math.min(min, layer.zIndex),
      0
    );
    const target = scene.layers.find((layer) => layer.id === layerId);
    if (!target || target.zIndex <= minZIndex) return;

    set(
      pushHistory(state, {
        scenes: updateActiveSceneLayers(
          state.scenes,
          state.activeSceneId,
          (layers) =>
            layers.map((layer) =>
              layer.id === layerId
                ? { ...layer, zIndex: minZIndex - 1 }
                : layer
            )
        ),
      })
    );
  },

  deleteLayer: (layerId) => {
    const state = get();
    set(
      pushHistory(state, {
        scenes: updateActiveSceneLayers(
          state.scenes,
          state.activeSceneId,
          (layers) => layers.filter((layer) => layer.id !== layerId)
        ),
        selectedLayerId:
          state.selectedLayerId === layerId ? null : state.selectedLayerId,
      })
    );
    scheduleActiveSceneThumbnailUpdate();
  },

  copyLayer: (layerId) => {
    const scene = getActiveScene(get());
    const layer = scene?.layers.find((item) => item.id === layerId);
    if (!layer) return;
    set({
      layerClipboard: {
        ...layer,
        props: { ...layer.props },
      },
    });
  },

  pasteLayer: () => {
    const state = get();
    const clip = state.layerClipboard;
    if (!clip) return;

    const sceneIndex = state.scenes.findIndex(
      (scene) => scene.id === state.activeSceneId
    );
    if (sceneIndex === -1) return;

    const scene = state.scenes[sceneIndex];
    const maxZIndex = scene.layers.reduce(
      (max, layer) => Math.max(max, layer.zIndex),
      0
    );

    const layer: Layer = {
      ...clip,
      id: createId(),
      x: clip.x + 24,
      y: clip.y + 24,
      zIndex: maxZIndex + 1,
      props: { ...clip.props },
    };

    const nextScenes = [...state.scenes];
    nextScenes[sceneIndex] = {
      ...scene,
      layers: [...scene.layers, layer],
    };

    set(
      pushHistory(state, {
        scenes: nextScenes,
        selectedLayerId: layer.id,
      })
    );
  },

  setSelectedLayer: (layerId) => set({ selectedLayerId: layerId }),

  undo: () => {
    const state = get();
    if (state.past.length === 0) return;

    const previous = state.past[state.past.length - 1];
    const current = captureHistorySnapshot(state);

    set({
      ...previous,
      scenes: cloneScenes(previous.scenes),
      past: state.past.slice(0, -1),
      future: [current, ...state.future].slice(0, STUDIO_HISTORY_LIMIT),
    });
  },

  redo: () => {
    const state = get();
    if (state.future.length === 0) return;

    const next = state.future[0];
    const current = captureHistorySnapshot(state);

    set({
      ...next,
      scenes: cloneScenes(next.scenes),
      past: [...state.past, current].slice(-STUDIO_HISTORY_LIMIT),
      future: state.future.slice(1),
    });
  },

  togglePlay: () => set((state) => ({ isPlaying: !state.isPlaying })),

  startPlayback: () => {
    const state = get();
    const max = getProjectDuration(state.scenes);
    const time = state.currentTime >= max ? 0 : state.currentTime;
    const scene = getSceneAtTime(state.scenes, time);

    set({
      isPlaying: true,
      currentTime: time,
      activeSceneId: scene?.id ?? state.activeSceneId,
      selectedLayerId: null,
    });
  },

  stopPlayback: () => set({ isPlaying: false }),

  setCurrentTime: (time) => {
    const max = getProjectDuration(get().scenes);
    set({ currentTime: Math.min(Math.max(0, time), max) });
  },

  setPxPerSecond: (pxPerSecond) => set({ pxPerSecond }),

  timelineZoomIn: () => {
    set({ pxPerSecond: getNextZoomLevel(get().pxPerSecond, "in") });
  },

  timelineZoomOut: () => {
    set({ pxPerSecond: getNextZoomLevel(get().pxPerSecond, "out") });
  },

  setAudioTrack: (fileName, src) =>
    set({ audioFileName: fileName, audioSrc: src }),

  clearAudioTrack: () =>
    set({ audioFileName: null, audioSrc: null, audioVolume: 100 }),

  setAudioVolume: (volume) =>
    set({ audioVolume: Math.min(100, Math.max(0, volume)) }),

  setSceneBackgroundColor: (color) => set({ sceneBackgroundColor: color }),

  setSceneAccentColor: (color) => set({ sceneAccentColor: color }),

  applyPaletteToAllScenes: (mainColor, accentColor) => {
    const state = get();

    // Derive a readable text colour from the main background
    const textColor = contrastTextColor(mainColor);

    // A shape is the "full-canvas background" when it fills almost the entire stage
    const isCanvasBg = (layer: Layer) =>
      layer.type === "shape" &&
      layer.zIndex === 0 &&
      layer.x <= 10 &&
      layer.y <= 10 &&
      layer.width >= 1200 &&
      layer.height >= 600;

    // A shape is a dark overlay when it has low opacity and sits above the bg
    const isDarkOverlay = (layer: Layer) =>
      layer.type === "shape" &&
      layer.zIndex >= 2 &&
      layer.opacity <= 0.65;

    const nextScenes = state.scenes.map((scene) => ({
      ...scene,
      layers: scene.layers.map((layer) => {
        if (isCanvasBg(layer)) {
          // Main background → use mainColor
          return {
            ...layer,
            props: {
              ...layer.props,
              fill: mainColor,
              stroke: mainColor,
            },
          };
        }
        if (isDarkOverlay(layer)) {
          // Keep the overlay as-is — just ensure it stays dark
          return layer;
        }
        if (layer.type === "shape") {
          // Accent shapes → use accentColor
          return {
            ...layer,
            props: {
              ...layer.props,
              fill: accentColor,
              stroke: accentColor,
            },
          };
        }
        if (layer.type === "text") {
          // Text → auto-contrast against the main background
          return {
            ...layer,
            props: {
              ...layer.props,
              fill: textColor,
            },
          };
        }
        return layer;
      }),
    }));

    set(
      pushHistory(state, {
        scenes: nextScenes,
        sceneBackgroundColor: mainColor,
        sceneAccentColor: accentColor,
      })
    );
    scheduleActiveSceneThumbnailUpdate();
  },

  applyTransitionToAllScenes: (transitionType) => {
    set({
      scenes: get().scenes.map((scene) => ({ ...scene, transitionType })),
    });
  },

  applyFontFamilyToAllTextLayers: (fontFamily) => {
    const state = get();
    const nextScenes = state.scenes.map((scene) => ({
      ...scene,
      layers: scene.layers.map((layer) =>
        layer.type !== "text"
          ? layer
          : {
              ...layer,
              props: { ...layer.props, fontFamily },
            }
      ),
    }));

    set(
      pushHistory(state, {
        scenes: nextScenes,
      })
    );
    scheduleActiveSceneThumbnailUpdate();
  },

  hydrateFromSceneData: (sceneData) => {
    const parsed = parseVideoSceneData(sceneData);
    if (!parsed) return false;

    set({
      scenes: parsed.scenes,
      activeSceneId: parsed.activeSceneId,
      selectedLayerId: null,
      isPlaying: false,
      currentTime: parsed.currentTime ?? 0,
      ...(parsed.pxPerSecond !== undefined
        ? { pxPerSecond: parsed.pxPerSecond }
        : {}),
      audioFileName: parsed.audioFileName ?? null,
      audioSrc: parsed.audioSrc ?? null,
      audioVolume: parsed.audioVolume ?? 100,
      sceneBackgroundColor:
        parsed.sceneBackgroundColor ?? DEFAULT_SCENE_BACKGROUND_COLOR,
      sceneAccentColor:
        parsed.sceneAccentColor ?? DEFAULT_SCENE_ACCENT_COLOR,
      chooseMode: parsed.chooseMode ?? "",
      past: [],
      future: [],
    });
    return true;
  },

  getSceneDataForSave: () => {
    const state = get();
    return buildVideoSceneDataPayload({
      scenes: state.scenes,
      activeSceneId: state.activeSceneId,
      currentTime: state.currentTime,
      pxPerSecond: state.pxPerSecond,
      audioFileName: state.audioFileName,
      audioSrc: state.audioSrc,
      audioVolume: state.audioVolume,
      sceneBackgroundColor: state.sceneBackgroundColor,
      sceneAccentColor: state.sceneAccentColor,
    });
  },
  };
});

export function getActiveScene(
  state: Pick<StudioState, "scenes" | "activeSceneId">
): Scene | undefined {
  return state.scenes.find((scene) => scene.id === state.activeSceneId);
}

export function getSelectedLayer(
  state: Pick<StudioState, "scenes" | "activeSceneId" | "selectedLayerId">
): Layer | undefined {
  const scene = getActiveScene(state);
  if (!scene || !state.selectedLayerId) return undefined;
  return scene.layers.find((layer) => layer.id === state.selectedLayerId);
}
