import { Schema } from "effect";
import { computed, ref, shallowRef, watch } from "vue";
import { toast } from "vue-sonner";
import { findFreePlacement, normalizeLayoutSet, placeGroupAt } from "../../share/layout.js";
import { readClip, reidFragment, writeClip } from "../../share/clipboard.js";
import { PageConfig } from "./schema.js";
import { BLOCKS, findBlock } from "./utils/resolve.js";
import { formatValidationError } from "./utils/validation-error.js";
function deepClone(value) {
  return JSON.parse(JSON.stringify(value));
}
function makeId() {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    const r = Math.random() * 16 | 0;
    return (c === "x" ? r : r & 3 | 8).toString(16);
  });
}
export function useSlotEditor(slot, options = {}) {
  const configure = options.configure ?? (() => {
  });
  const blocks = computed(
    () => slot.value.blocks
  );
  const layouts = computed({
    get: () => [...slot.value.layouts],
    set: (next) => {
      slot.value = { ...slot.value, layouts: next };
    }
  });
  function setLayouts(next) {
    layouts.value = next;
  }
  const selectedBlockIds = ref([]);
  const activeLayoutIndex = ref(0);
  watch(layouts, (next) => {
    if (next.length === 0) {
      activeLayoutIndex.value = 0;
      return;
    }
    if (activeLayoutIndex.value >= next.length) {
      activeLayoutIndex.value = next.length - 1;
    }
    if (activeLayoutIndex.value < 0) activeLayoutIndex.value = 0;
  });
  const pickerEntries = computed(() => {
    const byType = /* @__PURE__ */ new Map();
    for (const b of BLOCKS) {
      const prev = byType.get(b.type);
      if (!prev || b.compatibilityDate > prev.compatibilityDate) {
        byType.set(b.type, {
          type: b.type,
          compatibilityDate: b.compatibilityDate,
          name: b.metadata.name,
          icon: b.metadata.icon
        });
      }
    }
    return [...byType.values()].sort((a, b) => a.name.localeCompare(b.name, "zh"));
  });
  function addBlock(entry, opts) {
    const id = makeId();
    const meta = findBlock(entry.type, entry.compatibilityDate);
    const block = {
      id,
      type: entry.type,
      compatibilityDate: entry.compatibilityDate,
      ...meta?.defaultBody() ?? {}
    };
    const w = meta?.metadata.w.initial ?? 1;
    const h = meta?.metadata.h.initial ?? 1;
    const i = activeLayoutIndex.value;
    const nextLayouts = slot.value.layouts.map((ls, idx) => {
      if (idx !== i) return ls;
      const placement = findFreePlacement(ls.layout.placements, w, h);
      const placements = { ...ls.layout.placements, [id]: placement };
      let cols = 0;
      let rows = 0;
      for (const p of Object.values(placements)) {
        const [, [x2, y2]] = p.area;
        if (x2 - 1 > cols) cols = x2 - 1;
        if (y2 - 1 > rows) rows = y2 - 1;
      }
      return {
        ...ls,
        layout: {
          ...ls.layout,
          placements,
          columns: Math.max(1, cols),
          rows: Math.max(1, rows)
        }
      };
    });
    slot.value = {
      ...slot.value,
      blocks: [...slot.value.blocks, block],
      layouts: nextLayouts
    };
    if (!opts?.silent) {
      selectedBlockIds.value = [id];
    }
    return id;
  }
  function addBlocks(newBlocks) {
    if (newBlocks.length === 0) return;
    slot.value = { ...slot.value, blocks: [...slot.value.blocks, ...newBlocks] };
  }
  function addBlocksWithPlacements(newBlocks, placements, target) {
    if (newBlocks.length === 0) return;
    const i = activeLayoutIndex.value;
    const nextLayouts = slot.value.layouts.map((ls, idx) => {
      if (idx !== i) return ls;
      const shifted = placeGroupAt(ls.layout.placements, placements, target ?? { x: 1, y: 1 });
      return normalizeLayoutSet({
        ...ls,
        layout: { ...ls.layout, placements: { ...ls.layout.placements, ...shifted } }
      });
    });
    slot.value = {
      ...slot.value,
      blocks: [...slot.value.blocks, ...newBlocks],
      layouts: nextLayouts
    };
    selectedBlockIds.value = newBlocks.map((b) => b.id);
  }
  function removeBlock(id) {
    removeBlocks([id]);
  }
  function removeBlocks(ids) {
    if (ids.length === 0) return;
    const drop = new Set(ids);
    const nextBlocks = blocks.value.filter((b) => !drop.has(b.id));
    const nextLayouts = slot.value.layouts.map((ls) => {
      const entries = Object.entries(ls.layout.placements).filter(([pid]) => !drop.has(pid));
      if (entries.length === Object.keys(ls.layout.placements).length) return ls;
      return normalizeLayoutSet({
        ...ls,
        layout: { ...ls.layout, placements: Object.fromEntries(entries) }
      });
    });
    slot.value = { ...slot.value, blocks: nextBlocks, layouts: nextLayouts };
    if (selectedBlockIds.value.some((x) => drop.has(x))) {
      selectedBlockIds.value = selectedBlockIds.value.filter((x) => !drop.has(x));
    }
  }
  function updateBlock(id, next) {
    const updated = blocks.value.map((b) => b.id === id ? next : b);
    slot.value = { ...slot.value, blocks: updated };
  }
  function getBlockLabel(b) {
    const entry = findBlock(b.type, b.compatibilityDate);
    const typeName = entry?.metadata.name ?? "\u672A\u547D\u540D\u5757";
    const displayName = b.displayName;
    return typeof displayName === "string" && displayName.length > 0 ? displayName : typeName;
  }
  function findBlockMeta(type, compatibilityDate) {
    return findBlock(type, compatibilityDate);
  }
  const layoutsAccessor = {
    get value() {
      return layouts.value;
    },
    set value(next) {
      layouts.value = next;
    }
  };
  return {
    selectedBlockIds,
    activeLayoutIndex,
    blocks,
    layouts: layoutsAccessor,
    pickerEntries,
    addBlock,
    addBlocks,
    addBlocksWithPlacements,
    removeBlock,
    removeBlocks,
    updateBlock,
    setLayouts,
    getBlockLabel,
    findBlockMeta,
    configure
  };
}
export function useSlotClipboard(target) {
  async function copy(ids) {
    const want = new Set(ids);
    const items = target.blocks.value.filter((b) => want.has(b.id));
    if (items.length === 0) return;
    const ls = target.layouts.value[target.activeLayoutIndex.value];
    const placements = {};
    if (ls) {
      for (const id of ids) {
        const p = ls.layout.placements[id];
        if (p) placements[id] = p;
      }
    }
    if (!await writeClip("block", items, placements)) {
      toast.error("\u590D\u5236\u5931\u8D25");
      return;
    }
    toast.success(`\u5DF2\u590D\u5236 ${items.length} \u4E2A\u5757`);
  }
  async function paste(anchor) {
    const payload = await readClip();
    if (!payload || payload.surface !== "block") return;
    const { items, placements } = reidFragment(payload.items, payload.placements);
    const pasted = items;
    if (pasted.length === 0) return;
    if (placements && Object.keys(placements).length > 0) {
      target.addBlocksWithPlacements(pasted, placements, anchor);
    } else {
      target.addBlocks(pasted);
    }
    toast.success(`\u5DF2\u7C98\u8D34 ${pasted.length} \u4E2A\u5757`);
  }
  return { copy, paste };
}
export function useConfigEditor(model, options = {}) {
  const configure = options.configure ?? (() => {
  });
  const baseline = ref(deepClone(model.value));
  const draft = ref(deepClone(baseline.value));
  const isDirty = computed(() => {
    return JSON.stringify(draft.value) !== JSON.stringify(baseline.value);
  });
  const topSlot = computed({
    get: () => draft.value.slot,
    set: (next) => {
      draft.value = { ...draft.value, slot: next };
    }
  });
  const topSlotEditor = useSlotEditor(topSlot, { configure });
  const stack = ref([{ kind: "layout" }]);
  const top = computed(() => stack.value[stack.value.length - 1]);
  const activeBlock = computed(() => {
    const t = top.value;
    if (t.kind !== "block") return null;
    return topSlotEditor.blocks.value.find((b) => b.id === t.blockId) ?? null;
  });
  const activeBlockEntry = computed(() => {
    const b = activeBlock.value;
    return b ? findBlock(b.type, b.compatibilityDate) ?? null : null;
  });
  watch(
    () => topSlotEditor.blocks.value.map((b) => b.id),
    (ids) => {
      const set = new Set(ids);
      const next = [];
      for (const e of stack.value) {
        if (e.kind === "block" && !set.has(e.blockId)) break;
        next.push(e);
      }
      if (next.length !== stack.value.length) stack.value = next;
      if (topSlotEditor.selectedBlockIds.value.some((id) => !set.has(id))) {
        topSlotEditor.selectedBlockIds.value = topSlotEditor.selectedBlockIds.value.filter((id) => set.has(id));
      }
    },
    { flush: "sync" }
  );
  const subCrumbs = shallowRef([]);
  const parentSuffixes = shallowRef([]);
  function addCrumb() {
    const crumb = {
      label: ref(null),
      back: ref(null),
      dispose: () => {
        subCrumbs.value = subCrumbs.value.filter((c) => c !== crumb);
      }
    };
    subCrumbs.value = [...subCrumbs.value, crumb];
    return crumb;
  }
  function addParentSuffix() {
    const entry = {
      value: ref(null),
      dispose: () => {
        parentSuffixes.value = parentSuffixes.value.filter((s) => s !== entry);
      }
    };
    parentSuffixes.value = [...parentSuffixes.value, entry];
    return entry;
  }
  function unwindSubCrumbsAfter(fromIndex) {
    const snapshot = [...subCrumbs.value];
    for (let i = snapshot.length - 1; i > fromIndex; i--) {
      const cb = snapshot[i].back.value;
      snapshot[i].back.value = null;
      snapshot[i].label.value = null;
      if (cb) cb();
    }
  }
  function unwindSubCrumbs() {
    unwindSubCrumbsAfter(-1);
  }
  const breadcrumbs = computed(() => {
    const suffixes = parentSuffixes.value.map((s) => s.value.value).filter((v) => !!v);
    const suffixDecoration = suffixes.length ? `\uFF08${suffixes.join("\u3001")}\uFF09` : "";
    let lastBlockStackIndex = -1;
    for (let i = stack.value.length - 1; i >= 0; i--) {
      if (stack.value[i].kind === "block") {
        lastBlockStackIndex = i;
        break;
      }
    }
    const base = stack.value.map((entry, i) => {
      if (entry.kind === "layout") return { kind: "layout", label: "\u5E03\u5C40", index: i };
      const b = topSlotEditor.blocks.value.find((x) => x.id === entry.blockId);
      const baseLabel = b ? topSlotEditor.getBlockLabel(b) : "\u5757";
      const label = i === lastBlockStackIndex ? `${baseLabel}${suffixDecoration}` : baseLabel;
      return {
        kind: "block",
        label,
        index: i,
        blockId: entry.blockId
      };
    });
    const subs = subCrumbs.value;
    const visibleFlags = subs.map((c) => !!c.label.value);
    let deeperHasBack = false;
    const clickable = subs.map(() => false);
    for (let i = subs.length - 1; i >= 0; i--) {
      clickable[i] = deeperHasBack;
      if (visibleFlags[i] && subs[i].back.value) deeperHasBack = true;
    }
    for (let i = 0; i < subs.length; i++) {
      if (visibleFlags[i]) {
        base.push({
          kind: "sub",
          label: subs[i].label.value,
          subIndex: i,
          clickable: clickable[i]
        });
      }
    }
    return base;
  });
  function goTo(index) {
    if (subCrumbs.value.some((c) => c.back.value)) {
      unwindSubCrumbs();
    }
    if (index < 0 || index >= stack.value.length - 1) return;
    stack.value = stack.value.slice(0, index + 1);
  }
  function goToSub(subIndex) {
    if (subIndex < 0 || subIndex >= subCrumbs.value.length) return;
    unwindSubCrumbsAfter(subIndex);
  }
  function popOne() {
    if (subCrumbs.value.some((c) => c.back.value)) {
      unwindSubCrumbs();
      return;
    }
    if (stack.value.length <= 1) return;
    stack.value = stack.value.slice(0, -1);
  }
  function pushBlock(blockId) {
    if (!topSlotEditor.blocks.value.some((b) => b.id === blockId)) return;
    stack.value = [...stack.value, { kind: "block", blockId }];
    topSlotEditor.selectedBlockIds.value = [blockId];
  }
  function addBlock(entry, opts) {
    topSlotEditor.addBlock(entry, opts);
  }
  function updateActiveBlock(next) {
    const id = activeBlock.value?.id;
    if (!id) return;
    topSlotEditor.updateBlock(id, next);
  }
  function onDrillDown(blockId) {
    pushBlock(blockId);
  }
  const validationError = ref(null);
  function confirm() {
    const schema = PageConfig(configure);
    try {
      const decoded = Schema.decodeUnknownSync(schema)(draft.value);
      validationError.value = null;
      model.value = decoded;
      baseline.value = deepClone(decoded);
      draft.value = deepClone(decoded);
      stack.value = [{ kind: "layout" }];
      topSlotEditor.selectedBlockIds.value = [];
      topSlotEditor.activeLayoutIndex.value = 0;
      subCrumbs.value = [];
      parentSuffixes.value = [];
      options.onClose?.();
    } catch (err) {
      validationError.value = formatValidationError(err, (i) => {
        const b = topSlotEditor.blocks.value[i];
        return b ? topSlotEditor.getBlockLabel(b) : void 0;
      });
    }
  }
  function reset() {
    baseline.value = deepClone(model.value);
    draft.value = deepClone(model.value);
    stack.value = [{ kind: "layout" }];
    topSlotEditor.selectedBlockIds.value = [];
    topSlotEditor.activeLayoutIndex.value = 0;
    validationError.value = null;
    subCrumbs.value = [];
    parentSuffixes.value = [];
  }
  function dismiss() {
    reset();
    options.onClose?.();
  }
  watch(draft, () => {
    if (validationError.value) validationError.value = null;
  }, { deep: true });
  return {
    draft,
    stack,
    selectedBlockIds: topSlotEditor.selectedBlockIds,
    activeLayoutIndex: topSlotEditor.activeLayoutIndex,
    validationError,
    addCrumb,
    addParentSuffix,
    isDirty,
    blocks: topSlotEditor.blocks,
    layouts: topSlotEditor.layouts,
    top,
    activeBlock,
    activeBlockEntry,
    breadcrumbs,
    pickerEntries: topSlotEditor.pickerEntries,
    configure,
    goTo,
    goToSub,
    popOne,
    pushBlock,
    addBlock,
    addBlocks: topSlotEditor.addBlocks,
    addBlocksWithPlacements: topSlotEditor.addBlocksWithPlacements,
    removeBlock: topSlotEditor.removeBlock,
    removeBlocks: topSlotEditor.removeBlocks,
    updateActiveBlock,
    setLayouts: topSlotEditor.setLayouts,
    getBlockLabel: topSlotEditor.getBlockLabel,
    findBlockMeta: topSlotEditor.findBlockMeta,
    onDrillDown,
    confirm,
    dismiss,
    reset
  };
}
