<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
import Muuri from 'muuri';
import { useLayoutStore } from '@/store/useLayoutStore';
import { useWidgetsStore } from '@/store/useWidgetsStore';
import { useUIStore } from '@/store/useUIStore';
import IconCard from '@/components/IconCard/index.vue';
import WidgetCard from '@/components/WidgetCard/index.vue';

type GridItemType = 'icon' | 'widget';
type GridItemSize = '1x1' | '1x2' | '2x1' | '2x2' | '2x4';

interface Icon {
  id: string;
  name: string;
  url: string;
  img?: string;
  bgColor?: string;
  size?: GridItemSize;
}

type WidgetSize = GridItemSize;
interface Widget {
  id: string;
  component: string;
  size: WidgetSize;
  data?: any;
}

interface GridOrderEntry {
  id: string;
  type: GridItemType;
}

type OrderedGridItem =
  | { type: 'icon'; id: string; icon: Icon }
  | { type: 'widget'; id: string; widget: Widget };

const GRID_ORDER_STORAGE_KEY = 'itab_grid_order';

const layoutStore = useLayoutStore();
const widgetsStore = useWidgetsStore();
const uiStore = useUIStore();
const gridRef = ref<HTMLElement | null>(null);
const grid = ref<any | null>(null);
const isDragging = ref(false);
const suppressClick = ref(false);
const clickBlockUntil = ref(0);
const gridOrder = ref<GridOrderEntry[]>([]);
const resizedKeys = ref(new Set<string>());
const pendingResizedKeys = new Set<string>();
const widgetSizeMap = new Map<string, GridItemSize>();
const iconSizeMap = new Map<string, GridItemSize>();
const layoutAnimationMs = 240;
const layoutEasing = 'cubic-bezier(0.4, 0, 0.2, 1)';

const buildDefaultOrder = (): GridOrderEntry[] => [
  ...widgetsStore.widgets.map(widget => ({ id: widget.id, type: 'widget' as const })),
  ...layoutStore.icons.map(icon => ({ id: icon.id, type: 'icon' as const })),
];

const persistGridOrder = (order: GridOrderEntry[]) => {
  gridOrder.value = order;
  localStorage.setItem(GRID_ORDER_STORAGE_KEY, JSON.stringify(order));
  const iconOrder = order.filter(item => item.type === 'icon').map(item => item.id);
  const widgetOrder = order.filter(item => item.type === 'widget').map(item => item.id);
  if (iconOrder.length) {
    layoutStore.setIconOrder(iconOrder);
  }
  if (widgetOrder.length) {
    widgetsStore.setWidgetOrder(widgetOrder);
  }
};

const loadGridOrder = () => {
  const saved = localStorage.getItem(GRID_ORDER_STORAGE_KEY);
  if (saved) {
    try {
      const parsed = JSON.parse(saved) as GridOrderEntry[];
      if (Array.isArray(parsed)) {
        gridOrder.value = parsed.filter(item => item?.id && (item.type === 'icon' || item.type === 'widget'));
      }
    } catch {
      gridOrder.value = [];
    }
  }
  if (!gridOrder.value.length) {
    gridOrder.value = buildDefaultOrder();
  }
  persistGridOrder(gridOrder.value);
};

const ensureOrderConsistency = () => {
  const iconIds = new Set(layoutStore.icons.map(icon => icon.id));
  const widgetIds = new Set(widgetsStore.widgets.map(widget => widget.id));
  const nextOrder: GridOrderEntry[] = [];

  for (const entry of gridOrder.value) {
    if (entry.type === 'icon' && iconIds.has(entry.id)) {
      nextOrder.push(entry);
    }
    if (entry.type === 'widget' && widgetIds.has(entry.id)) {
      nextOrder.push(entry);
    }
  }

  for (const widget of widgetsStore.widgets) {
    if (!nextOrder.some(item => item.type === 'widget' && item.id === widget.id)) {
      nextOrder.push({ id: widget.id, type: 'widget' });
    }
  }

  for (const icon of layoutStore.icons) {
    if (!nextOrder.some(item => item.type === 'icon' && item.id === icon.id)) {
      nextOrder.push({ id: icon.id, type: 'icon' });
    }
  }

  const orderChanged =
    nextOrder.length !== gridOrder.value.length ||
    nextOrder.some((entry, index) => {
      const current = gridOrder.value[index];
      return current?.id !== entry.id || current?.type !== entry.type;
    });

  if (orderChanged) {
    persistGridOrder(nextOrder);
  } else {
    gridOrder.value = nextOrder;
  }
};

const orderedItems = computed<OrderedGridItem[]>(() => {
  const iconsById = new Map(layoutStore.icons.map(icon => [icon.id, icon]));
  const widgetsById = new Map(widgetsStore.widgets.map(widget => [widget.id, widget]));
  const used = new Set<string>();
  const items: OrderedGridItem[] = [];

  for (const entry of gridOrder.value) {
    if (entry.type === 'icon') {
      const icon = iconsById.get(entry.id);
      if (icon) {
        items.push({ type: 'icon', id: icon.id, icon });
        used.add(`icon:${icon.id}`);
      }
    } else {
      const widget = widgetsById.get(entry.id);
      if (widget) {
        items.push({ type: 'widget', id: widget.id, widget });
        used.add(`widget:${widget.id}`);
      }
    }
  }

  for (const widget of widgetsStore.widgets) {
    const key = `widget:${widget.id}`;
    if (!used.has(key)) {
      items.push({ type: 'widget', id: widget.id, widget });
    }
  }

  for (const icon of layoutStore.icons) {
    const key = `icon:${icon.id}`;
    if (!used.has(key)) {
      items.push({ type: 'icon', id: icon.id, icon });
    }
  }

  return items;
});

const handleClick = (event: MouseEvent) => {
  if (suppressClick.value || isDragging.value) return;
  const target = event.target as HTMLElement;
  const itemEl = target.closest('.grid-item') as HTMLElement | null;
  if (!itemEl || itemEl.dataset.type !== 'icon') return;
  const link = target.closest('a') as HTMLAnchorElement | null;
  if (link && link.href) {
    window.open(link.href, '_blank');
  }
};

const handleClickCapture = (event: MouseEvent) => {
  if (isDragging.value || suppressClick.value || Date.now() < clickBlockUntil.value) {
    event.preventDefault();
    event.stopPropagation();
  }
};

const handleContextMenu = (event: MouseEvent) => {
  const target = event.target as HTMLElement;
  const itemEl = target.closest('.grid-item') as HTMLElement | null;
  if (!itemEl) return;
  const id = itemEl.dataset.id;
  const type = itemEl.dataset.type as GridItemType | undefined;
  if (id && type) {
    uiStore.openContextMenu(event.clientX, event.clientY, id, type);
  }
};

const persistOrderFromGrid = async () => {
  if (!grid.value) return;
  const items = grid.value.getItems();
  const nextOrder: GridOrderEntry[] = [];
  for (const item of items) {
    const element = item.getElement() as HTMLElement;
    const id = element.dataset.id;
    const type = element.dataset.type as GridItemType | undefined;
    if (id && (type === 'icon' || type === 'widget')) {
      nextOrder.push({ id, type });
    }
  }
  if (nextOrder.length) {
    persistGridOrder(nextOrder);
  }
  await nextTick();
  grid.value?.synchronize();
  grid.value?.layout();
};

const refreshLayout = async (instant = false) => {
  await nextTick();
  grid.value?.refreshItems();
  if (instant) {
    grid.value?.layout(true);
  } else {
    grid.value?.layout();
  }
};

const markResized = (type: GridItemType, id: string) => {
  pendingResizedKeys.add(`${type}:${id}`);
};

const syncSizeMap = (
  items: { id: string; size: GridItemSize }[],
  map: Map<string, GridItemSize>
) => {
  const changed: string[] = [];
  const seen = new Set<string>();
  for (const item of items) {
    const prev = map.get(item.id);
    if (prev && prev !== item.size) {
      changed.push(item.id);
    }
    map.set(item.id, item.size);
    seen.add(item.id);
  }
  for (const id of Array.from(map.keys())) {
    if (!seen.has(id)) {
      map.delete(id);
    }
  }
  return changed;
};

onMounted(async () => {
  loadGridOrder();
  await nextTick();
  if (!gridRef.value) return;
  grid.value = new Muuri(gridRef.value, {
    items: '.grid-item',
    dragEnabled: true,
    dragStartPredicate: {
      distance: 5,
      delay: 0,
    },
    dragSort: true,
    layout: {
      fillGaps: true,
      rounding: true,
    },
    layoutDuration: layoutAnimationMs,
    layoutEasing,
  });

  grid.value.on('dragStart', () => {
    isDragging.value = true;
    suppressClick.value = true;
  });

  grid.value.on('dragEnd', () => {
    isDragging.value = false;
    clickBlockUntil.value = Date.now() + 180;
    window.setTimeout(() => {
      suppressClick.value = false;
    }, 0);
  });

  grid.value.on('dragReleaseEnd', () => {
    persistOrderFromGrid();
  });

  grid.value.on('layoutStart', () => {
    if (!pendingResizedKeys.size) return;
    resizedKeys.value = new Set(pendingResizedKeys);
  });

  grid.value.on('layoutEnd', () => {
    if (!pendingResizedKeys.size) return;
    pendingResizedKeys.clear();
    resizedKeys.value = new Set<string>();
  });

  grid.value.layout(true);
});

watch(
  () => [layoutStore.icons.length, widgetsStore.widgets.length],
  () => {
    ensureOrderConsistency();
    refreshLayout();
  }
);

watch(
  () => widgetsStore.widgets.map(widget => ({ id: widget.id, size: widget.size })),
  async () => {
    const changed = syncSizeMap(
      widgetsStore.widgets.map(widget => ({ id: widget.id, size: widget.size })),
      widgetSizeMap
    );
    for (const id of changed) {
      markResized('widget', id);
    }
    await refreshLayout();
  }
);

watch(
  () => layoutStore.icons.map(icon => ({ id: icon.id, size: icon.size ?? '1x1' })),
  async () => {
    const changed = syncSizeMap(
      layoutStore.icons.map(icon => ({ id: icon.id, size: icon.size ?? '1x1' })),
      iconSizeMap
    );
    for (const id of changed) {
      markResized('icon', id);
    }
    await refreshLayout();
  }
);

onUnmounted(() => {
  pendingResizedKeys.clear();
  grid.value?.destroy();
  grid.value = null;
});
</script>

<template>
  <div
    ref="gridRef"
    class="grid-canvas"
    :style="{ '--layout-anim-ms': `${layoutAnimationMs}ms` }"
    @click.capture="handleClickCapture"
    @click="handleClick"
    @contextmenu.prevent="handleContextMenu"
  >
    <div
      v-for="item in orderedItems"
      :key="`${item.type}-${item.id}`"
      class="grid-item"
      :class="[
        `size-${item.type === 'widget' ? item.widget.size : (item.icon.size ?? '1x1')}`,
        { 'is-resized': resizedKeys.has(`${item.type}:${item.id}`) }
      ]"
      :data-id="item.id"
      :data-type="item.type"
    >
      <div class="grid-item-content">
        <WidgetCard v-if="item.type === 'widget'" :widget="item.widget" />
        <IconCard v-else :icon="item.icon" />
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
@import '@/styles/tokens.scss';

.grid-canvas {
  width: 100%;
  position: relative;
  user-select: none;          /* 标准 */
  -webkit-user-select: none;  /* Safari 浏览器 */
  -moz-user-select: none;     /* Firefox 浏览器 */
  -ms-user-select: none;      /* IE 10+/Edge 浏览器 */
  --cell-size: var(--grid-cell-size);
  --cell-gap: var(--grid-gap);
  padding: calc(var(--cell-gap) / 2);
  box-sizing: border-box;
}

.grid-item {
  position: absolute;
  width: var(--cell-size);
  height: var(--cell-size);
  margin: calc(var(--cell-gap) / 2);
  cursor: grab;
  will-change: transform;
}

.grid-item.is-resized {
  overflow: hidden;
  border-radius: $border-radius-small;
}

.grid-item.size-1x2 {
  height: calc(var(--cell-size) * 2 + var(--cell-gap));
}

.grid-item.size-2x1 {
  width: calc(var(--cell-size) * 2 + var(--cell-gap));
}

.grid-item.size-2x2 {
  width: calc(var(--cell-size) * 2 + var(--cell-gap));
  height: calc(var(--cell-size) * 2 + var(--cell-gap));
}

.grid-item.size-2x4 {
  width: calc(var(--cell-size) * 2 + var(--cell-gap));
  height: calc(var(--cell-size) * 4 + var(--cell-gap) * 3);
}

.grid-item-content {
  width: 100%;
  height: 100%;
  transform-origin: center;
}

.grid-item.is-resized .grid-item-content {
  animation: grid-item-resize-zoom var(--layout-anim-ms) $motion-easing-standard;
}

@keyframes grid-item-resize-zoom {
  0% {
    transform: scale(0.97);
  }
  100% {
    transform: scale(1);
  }
}

.grid-item.muuri-item-dragging {
  z-index: $z-index-menu;
  cursor: grabbing;
  transition: none;
}
</style>
