import { create } from "zustand";

import {
  cropImageDataUrl,
  fitCropRectToAspect,
  getBaseLayerSourceCrop,
} from "@/lib/image-editor-crop";
import type {
  CropRect,
  ExportImageFormat,
  FilterPresetId,
  ImageAdjustments,
  ImageCropAspectRatio,
  ImageLayer,
  ImageLayerType,
  ImagePanelTab,
  ImageShapeKind,
  ImageTool,
} from "@/lib/image-editor-types";
import {
  DEFAULT_ADJUSTMENTS,
  DEFAULT_CANVAS_SIZE,
} from "@/lib/image-editor-types";
import { getPresetById } from "@/lib/image-editor-filters";
import {
  buildImageSceneDataPayload,
  parseImageSceneData,
} from "@/lib/image-scene-data";

function createId(): string {
  return crypto.randomUUID();
}

function createLayer(
  type: ImageLayerType,
  zIndex: number,
  partial?: Partial<ImageLayer>
): ImageLayer {
  return {
    id: createId(),
    type,
    name: partial?.name ?? type,
    visible: true,
    x: partial?.x ?? 100,
    y: partial?.y ?? 100,
    width: partial?.width ?? 240,
    height: partial?.height ?? 120,
    rotation: partial?.rotation ?? 0,
    opacity: partial?.opacity ?? 1,
    zIndex,
    props: partial?.props ?? {},
  };
}

export interface ImageEditorState {
  canvasWidth: number;
  canvasHeight: number;
  layers: ImageLayer[];
  selectedLayerId: string | null;
  activeTool: ImageTool;
  activePanelTab: ImagePanelTab;
  adjustments: ImageAdjustments;
  activeFilterPreset: FilterPresetId;
  cropRect: CropRect | null;
  cropAspectRatio: ImageCropAspectRatio;
  exportFormat: ExportImageFormat;
  exportQuality: number;
  isAiModalOpen: boolean;
  pendingShape: ImageShapeKind;
}

export interface ImageEditorActions {
  setActiveTool: (tool: ImageTool) => void;
  setActivePanelTab: (tab: ImagePanelTab) => void;
  setSelectedLayer: (layerId: string | null) => void;
  setAdjustments: (updates: Partial<ImageAdjustments>) => void;
  applyFilterPreset: (presetId: FilterPresetId) => void;
  setCropRect: (rect: CropRect | null) => void;
  setCropAspectRatio: (ratio: ImageCropAspectRatio) => void;
  applyCrop: () => Promise<void>;
  cancelCrop: () => void;
  setCanvasSize: (width: number, height: number) => void;
  loadBaseImage: (src: string, width: number, height: number) => void;
  replaceBaseImage: (src: string) => void;
  addLayer: (partial: Partial<ImageLayer> & { type: ImageLayerType }) => void;
  updateLayer: (layerId: string, updates: Partial<ImageLayer>) => void;
  updateLayerProps: (layerId: string, props: Record<string, unknown>) => void;
  deleteLayer: (layerId: string) => void;
  toggleLayerVisibility: (layerId: string) => void;
  reorderLayers: (orderedIds: string[]) => void;
  setExportFormat: (format: ExportImageFormat) => void;
  setExportQuality: (quality: number) => void;
  setAiModalOpen: (open: boolean) => void;
  setPendingShape: (shape: ImageShapeKind) => void;
  hydrateFromSceneData: (sceneData: Record<string, unknown>) => boolean;
  getSceneDataForSave: () => Record<string, unknown>;
}

export type ImageEditorStore = ImageEditorState & ImageEditorActions;

function getBaseLayer(layers: ImageLayer[]): ImageLayer | undefined {
  return layers.find((layer) => layer.type === "image");
}

export const useImageEditorStore = create<ImageEditorStore>((set, get) => ({
  canvasWidth: DEFAULT_CANVAS_SIZE.width,
  canvasHeight: DEFAULT_CANVAS_SIZE.height,
  layers: [],
  selectedLayerId: null,
  activeTool: "select",
  activePanelTab: "adjust",
  adjustments: { ...DEFAULT_ADJUSTMENTS },
  activeFilterPreset: "original",
  cropRect: null,
  cropAspectRatio: "free",
  exportFormat: "png",
  exportQuality: 90,
  isAiModalOpen: false,
  pendingShape: "rect",

  setActiveTool: (tool) => {
    if (tool === "crop") {
      const { canvasWidth, canvasHeight } = get();
      set({
        activeTool: "crop",
        cropRect: { x: 0, y: 0, w: canvasWidth, h: canvasHeight },
        cropAspectRatio: "free",
        selectedLayerId: null,
        isAiModalOpen: false,
      });
      return;
    }
    set({
      activeTool: tool,
      cropRect: null,
      cropAspectRatio: "free",
      isAiModalOpen: tool === "ai",
    });
  },

  setActivePanelTab: (tab) => set({ activePanelTab: tab }),

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

  setAdjustments: (updates) => {
    set({
      adjustments: { ...get().adjustments, ...updates },
      activeFilterPreset: "original",
    });
  },

  applyFilterPreset: (presetId) => {
    const preset = getPresetById(presetId);
    set({
      activeFilterPreset: presetId,
      adjustments: { ...preset.adjustments },
    });
  },

  setCropRect: (rect) => set({ cropRect: rect }),

  setCropAspectRatio: (ratio) => {
    const { cropRect, canvasWidth, canvasHeight } = get();
    if (!cropRect) return;
    set({
      cropAspectRatio: ratio,
      cropRect: fitCropRectToAspect(
        cropRect,
        ratio,
        canvasWidth,
        canvasHeight
      ),
    });
  },

  cancelCrop: () =>
    set({
      activeTool: "select",
      cropRect: null,
      cropAspectRatio: "free",
    }),

  applyCrop: async () => {
    const { cropRect, layers } = get();
    if (!cropRect) return;

    const base = getBaseLayer(layers);
    let nextLayers = layers.map((layer) => ({
      ...layer,
      x: layer.x - cropRect.x,
      y: layer.y - cropRect.y,
    }));

    if (base && typeof base.props.src === "string") {
      const sourceCrop = getBaseLayerSourceCrop(base, cropRect);
      if (sourceCrop) {
        try {
          const cropped = await cropImageDataUrl(
            base.props.src,
            sourceCrop,
            base.width,
            base.height
          );
          nextLayers = nextLayers.map((layer) =>
            layer.id === base.id
              ? {
                  ...layer,
                  x: Math.max(0, base.x - cropRect.x),
                  y: Math.max(0, base.y - cropRect.y),
                  width: cropped.width,
                  height: cropped.height,
                  props: { ...layer.props, src: cropped.dataUrl },
                }
              : layer
          );
        } catch {
          const intersect = getBaseLayerSourceCrop(base, cropRect);
          if (intersect) {
            nextLayers = nextLayers.map((layer) =>
              layer.id === base.id
                ? {
                    ...layer,
                    x: Math.max(0, base.x - cropRect.x),
                    y: Math.max(0, base.y - cropRect.y),
                    width: intersect.w,
                    height: intersect.h,
                  }
                : layer
            );
          }
        }
      }
    }

    set({
      canvasWidth: Math.round(cropRect.w),
      canvasHeight: Math.round(cropRect.h),
      layers: nextLayers,
      cropRect: null,
      cropAspectRatio: "free",
      activeTool: "select",
      selectedLayerId: null,
    });
  },

  setCanvasSize: (width, height) => set({ canvasWidth: width, canvasHeight: height }),

  loadBaseImage: (src, width, height) => {
    const layer = createLayer("image", 0, {
      name: "Background",
      x: 0,
      y: 0,
      width,
      height,
      props: { src },
    });
    set({
      canvasWidth: width,
      canvasHeight: height,
      layers: [layer],
      selectedLayerId: layer.id,
      adjustments: { ...DEFAULT_ADJUSTMENTS },
      activeFilterPreset: "original",
    });
  },

  replaceBaseImage: (src) => {
    const base = getBaseLayer(get().layers);
    if (!base) return;
    get().updateLayerProps(base.id, { src });
  },

  addLayer: (partial) => {
    const maxZ = get().layers.reduce((m, l) => Math.max(m, l.zIndex), 0);
    const layer = createLayer(partial.type, maxZ + 1, partial);
    set({
      layers: [...get().layers, layer],
      selectedLayerId: layer.id,
      activeTool: "select",
    });
  },

  updateLayer: (layerId, updates) => {
    set({
      layers: get().layers.map((layer) =>
        layer.id === layerId ? { ...layer, ...updates } : layer
      ),
    });
  },

  updateLayerProps: (layerId, props) => {
    set({
      layers: get().layers.map((layer) =>
        layer.id === layerId
          ? { ...layer, props: { ...layer.props, ...props } }
          : layer
      ),
    });
  },

  deleteLayer: (layerId) => {
    const target = get().layers.find((l) => l.id === layerId);
    if (!target || target.type === "image") return;
    set({
      layers: get().layers.filter((layer) => layer.id !== layerId),
      selectedLayerId:
        get().selectedLayerId === layerId ? null : get().selectedLayerId,
    });
  },

  toggleLayerVisibility: (layerId) => {
    set({
      layers: get().layers.map((layer) =>
        layer.id === layerId ? { ...layer, visible: !layer.visible } : layer
      ),
    });
  },

  reorderLayers: (orderedIds) => {
    const map = new Map(get().layers.map((l) => [l.id, l]));
    const reordered = orderedIds
      .map((id, index) => {
        const layer = map.get(id);
        return layer ? { ...layer, zIndex: index } : null;
      })
      .filter((layer): layer is ImageLayer => layer !== null);
    set({ layers: reordered });
  },

  setExportFormat: (format) => set({ exportFormat: format }),
  setExportQuality: (quality) => set({ exportQuality: quality }),
  setAiModalOpen: (open) =>
    set({ isAiModalOpen: open, activeTool: open ? "ai" : "select" }),

  setPendingShape: (shape) => set({ pendingShape: shape }),

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

    set({
      canvasWidth: parsed.canvasWidth,
      canvasHeight: parsed.canvasHeight,
      layers: parsed.layers,
      adjustments: parsed.adjustments,
      activeFilterPreset: parsed.activeFilterPreset,
      selectedLayerId: null,
      activeTool: "select",
      cropRect: null,
      cropAspectRatio: "free",
      isAiModalOpen: false,
    });
    return true;
  },

  getSceneDataForSave: () => {
    const state = get();
    return buildImageSceneDataPayload({
      canvasWidth: state.canvasWidth,
      canvasHeight: state.canvasHeight,
      layers: state.layers,
      adjustments: state.adjustments,
      activeFilterPreset: state.activeFilterPreset,
    });
  },
}));

export function getBaseImageLayer(
  state: Pick<ImageEditorState, "layers">
): ImageLayer | undefined {
  return getBaseLayer(state.layers);
}
