<script setup>
import { computed, effectScope, inject, onBeforeUnmount, ref, shallowRef, watch } from "vue";
import { Icon } from "@iconify/vue";
import { useI18n } from "vue-i18n";
import { regenSlotIds, defaultSlot } from "../../../../../share/layout";
import { getLocalizedText } from "../../../../../share/locale";
import BlockLayoutEditor from "../../../../block-layout-editor/index.vue";
import LayoutsSidebar from "../../../../block-layout-editor/sidebar.vue";
import LayoutMetaStrip from "../../../../block-layout-editor/meta-strip.vue";
import { Button } from "../../../../ui/button";
import { Field, FieldLabel } from "../../../../ui/field";
import { Locale as LocaleField } from "../../../../ui/locale";
import { Markdown } from "../../../../ui/markdown";
import { ScrollArea } from "../../../../ui/scroll-area";
import { Separator } from "../../../../ui/separator";
import { Textarea } from "../../../../ui/textarea";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../../../ui/tooltip";
import {
  useTreeDnd
} from "../../../../../composables/useTreeDnd";
import { BREADCRUMB_EXTENSION_KEY } from "../../../breadcrumb-extension";
import { findBlock } from "../../../utils/resolve";
import { useSlotClipboard, useSlotEditor } from "../../../use-editor";
import { TabPaneMeta, getStructFieldDescription, getStructFieldTitle } from "./schema";
defineOptions({ name: "ShwfedBlockTabsConfig" });
const block = defineModel({ type: null, ...{ required: true } });
const props = defineProps({
  configure: { type: Function, required: false }
});
const configure = computed(() => props.configure ?? (() => {
}));
const paneFieldTitle = (f) => getStructFieldTitle(TabPaneMeta, f) ?? f;
const paneFieldDescription = (f) => getStructFieldDescription(TabPaneMeta, f);
const { locale } = useI18n();
const tabs = computed(() => block.value.tabs);
const activePaneId = ref(tabs.value[0]?.id ?? "");
const viewMode = ref("pane");
function paneIndex(id) {
  return block.value.tabs.findIndex((t) => t.id === id);
}
function makePaneAccessor(id) {
  return computed({
    get: () => {
      const t = block.value.tabs.find((x) => x.id === id);
      return t?.slot ?? defaultSlot();
    },
    set: (next) => {
      const arr = block.value.tabs.map(
        (t) => t.id === id ? { ...t, slot: next } : t
      );
      block.value = { ...block.value, tabs: arr };
    }
  });
}
const paneShells = shallowRef(/* @__PURE__ */ new Map());
function buildShell(id) {
  const scope = effectScope(true);
  const editor = scope.run(
    () => useSlotEditor(makePaneAccessor(id), { configure: configure.value })
  );
  return {
    editor,
    scope,
    layouts: {
      get value() {
        return editor.layouts.value;
      },
      set value(next) {
        editor.layouts.value = next;
      }
    },
    active: {
      get value() {
        return editor.activeLayoutIndex.value;
      },
      set value(next) {
        editor.activeLayoutIndex.value = next;
      }
    },
    selected: {
      get value() {
        return editor.selectedBlockIds.value;
      },
      set value(next) {
        editor.selectedBlockIds.value = next;
      }
    },
    clipboard: useSlotClipboard(editor)
  };
}
function ensureShell(id) {
  if (!id) return null;
  const cached = paneShells.value.get(id);
  if (cached) return cached;
  const shell = buildShell(id);
  const next = new Map(paneShells.value);
  next.set(id, shell);
  paneShells.value = next;
  return shell;
}
watch(tabs, (next) => {
  if (next.length === 0) {
    activePaneId.value = "";
  } else if (!next.some((t) => t.id === activePaneId.value)) {
    activePaneId.value = next[0].id;
  }
  const valid = new Set(next.map((t) => t.id));
  let prune = false;
  for (const k of paneShells.value.keys()) {
    if (!valid.has(k)) {
      prune = true;
      break;
    }
  }
  if (prune) {
    const filtered = /* @__PURE__ */ new Map();
    for (const [k, v] of paneShells.value.entries()) {
      if (valid.has(k)) filtered.set(k, v);
      else v.scope.stop();
    }
    paneShells.value = filtered;
  }
}, { flush: "post" });
onBeforeUnmount(() => {
  for (const shell of paneShells.value.values()) shell.scope.stop();
});
const activePane = computed(
  () => block.value.tabs.find((t) => t.id === activePaneId.value) ?? null
);
const activeShell = computed(() => ensureShell(activePaneId.value));
function paneLabel(t, index) {
  return getLocalizedText(t.label, locale.value) ?? `\u6807\u7B7E\u9875 ${index + 1}`;
}
function selectPane(id) {
  viewMode.value = "pane";
  activePaneId.value = id;
  drilledBlockId.value = null;
}
function selectGeneral() {
  viewMode.value = "general";
  drilledBlockId.value = null;
}
function updateBlockStyle(value) {
  const next = { ...block.value };
  if (value.trim() === "") {
    delete next.style;
  } else {
    next.style = value;
  }
  block.value = next;
}
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);
  });
}
function addPane() {
  const id = makeId();
  const index = block.value.tabs.length + 1;
  const next = {
    id,
    label: [{ locale: "zh", message: `\u6807\u7B7E ${index}` }],
    slot: defaultSlot()
  };
  block.value = { ...block.value, tabs: [...block.value.tabs, next] };
  activePaneId.value = id;
}
function duplicatePane(id) {
  const i = paneIndex(id);
  if (i < 0) return;
  const src = block.value.tabs[i];
  const dupSlot = regenSlotIds(src.slot);
  const newId = makeId();
  const dup = {
    id: newId,
    label: src.label,
    slot: dupSlot
  };
  const arr = [...block.value.tabs];
  arr.splice(i + 1, 0, dup);
  block.value = { ...block.value, tabs: arr };
  activePaneId.value = newId;
}
function deletePane(id) {
  if (block.value.tabs.length <= 1) return;
  const i = paneIndex(id);
  if (i < 0) return;
  const arr = block.value.tabs.filter((_, idx) => idx !== i);
  block.value = { ...block.value, tabs: arr };
  if (activePaneId.value === id) {
    activePaneId.value = arr[Math.max(0, i - 1)]?.id ?? "";
  }
}
function movePane(from, to) {
  if (from === to) return;
  if (from < 0 || to < 0) return;
  if (from >= block.value.tabs.length || to >= block.value.tabs.length) return;
  const next = [...block.value.tabs];
  const [item] = next.splice(from, 1);
  if (!item) return;
  next.splice(to, 0, item);
  block.value = { ...block.value, tabs: next };
}
function updatePaneLabel(id, value) {
  const arr = block.value.tabs.map(
    (t) => t.id === id ? { ...t, label: value } : t
  );
  block.value = { ...block.value, tabs: arr };
}
const drilledBlockId = ref(null);
const drilledBlock = computed(() => {
  const id = drilledBlockId.value;
  if (!id) return null;
  const ae = activeShell.value?.editor;
  if (!ae) return null;
  return ae.blocks.value.find((b) => b.id === id) ?? null;
});
const drilledEntry = computed(() => {
  const b = drilledBlock.value;
  return b ? findBlock(b.type, b.compatibilityDate) ?? null : null;
});
function onDrillDown(id) {
  drilledBlockId.value = id;
}
function popDrill() {
  drilledBlockId.value = null;
}
watch(
  () => activeShell.value?.editor.blocks.value.map((b) => b.id) ?? [],
  (ids) => {
    const id = drilledBlockId.value;
    if (id && !ids.includes(id)) drilledBlockId.value = null;
  }
);
function updateDrilledBlock(next) {
  const id = drilledBlockId.value;
  if (!id) return;
  activeShell.value?.editor.updateBlock(id, next);
}
const breadcrumbExt = inject(BREADCRUMB_EXTENSION_KEY, null);
if (breadcrumbExt) {
  const paneSuffix = breadcrumbExt.addParentSuffix();
  const blockCrumb = breadcrumbExt.add();
  watch(
    [activePane, drilledBlock, viewMode],
    ([pane, drilled, mode]) => {
      if (mode === "general" || !pane) {
        paneSuffix.value.value = null;
        blockCrumb.label.value = null;
        blockCrumb.back.value = null;
        return;
      }
      const i = paneIndex(pane.id);
      paneSuffix.value.value = paneLabel(pane, i < 0 ? 0 : i);
      if (drilled) {
        blockCrumb.label.value = activeShell.value?.editor.getBlockLabel(drilled) ?? null;
        blockCrumb.back.value = popDrill;
      } else {
        blockCrumb.label.value = null;
        blockCrumb.back.value = null;
      }
    },
    { immediate: true }
  );
  onBeforeUnmount(() => {
    paneSuffix.dispose();
    blockCrumb.dispose();
  });
}
const ROW_KIND = "tabs-pane-row";
function rowId(i) {
  return `pane-${i}`;
}
function onRowDrop(e) {
  if (e.source.kind !== ROW_KIND || e.target.kind !== ROW_KIND) return;
  const from = block.value.tabs.findIndex((_, i) => rowId(i) === e.source.id);
  const to = block.value.tabs.findIndex((_, i) => rowId(i) === e.target.id);
  if (from < 0 || to < 0) return;
  if (e.instruction === "reorder-above") movePane(from, from < to ? to - 1 : to);
  else if (e.instruction === "reorder-below") movePane(from, from <= to ? to : to + 1);
}
const dnd = useTreeDnd({ onRowDrop });
const pickDragHandle = (el) => el.querySelector(".drag-handle");
function rowConfig(i) {
  return {
    kind: ROW_KIND,
    canDrop: (src) => src.kind === ROW_KIND,
    blockInstructions: (src) => {
      const blocked = ["make-child", "reparent"];
      if (src.id === rowId(i)) blocked.push("reorder-above", "reorder-below");
      return blocked;
    },
    dragHandle: pickDragHandle
  };
}
function addLayoutToActive() {
  const s = activeShell.value;
  if (!s) return;
  const cur = s.layouts.value;
  s.layouts.value = [
    ...cur,
    { name: "\u65B0\u5E03\u5C40", layout: { columns: 1, placements: {} } }
  ];
  s.active.value = cur.length;
}
function addBlockToActive(entry, options) {
  activeShell.value?.editor.addBlock(entry, options);
}
function deleteBlockFromActive(id) {
  activeShell.value?.editor.removeBlock(id);
}
function deleteBlocksFromActive(ids) {
  activeShell.value?.editor.removeBlocks(ids);
}
</script>

<template>
  <div class="flex min-w-0 gap-2 min-h-128">
    <!-- Unified left rail: two equal halves stacked — panes (top) and the
         active pane's layouts (bottom). Each half has its own scroll area
         with its + button anchored at the foot, so the rail reads as two
         parallel lists rather than one long list. Hidden while drilled into
         a child block so the drilled view owns the full width. -->
    <div
      v-if="!drilledBlockId"
      class="flex w-56 shrink-0 flex-col"
    >
      <div
        class="row pl-2"
        :class="[
  viewMode === 'general' ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'
]"
        @click="selectGeneral()"
      >
        <Icon
          icon="fluent:settings-20-regular"
          class="size-4 shrink-0"
        />
        <span class="flex-1 truncate">通用配置</span>
      </div>

      <Separator class="my-2" />

      <!-- Panes -->
      <div class="flex flex-1 min-h-0 flex-col">
        <ScrollArea class="flex-1">
          <div class="flex flex-col gap-0.5">
            <div
              v-for="(t, i) in block.tabs"
              :key="t.id"
              :ref="dnd.rowRef(rowId(i), rowConfig(i))"
              class="row group"
              :class="[
  activePaneId === t.id ? 'bg-[color-mix(in_srgb,var(--primary)_10%,white)] text-(--primary)' : 'text-zinc-700 hover:bg-zinc-50'
]"
              :data-instruction="dnd.instructionFor(rowId(i)) ?? void 0"
              @click="selectPane(t.id)"
            >
              <Icon
                icon="fluent:re-order-dots-vertical-20-regular"
                class="drag-handle size-4 shrink-0 cursor-grab text-zinc-400"
              />
              <span class="flex-1 truncate">{{ paneLabel(t, i) }}</span>
              <span
                class="ml-auto flex items-center gap-0.5 opacity-0 transition-opacity group-hover:opacity-100"
                @click.stop
              >
                <Tooltip :delay-duration="180">
                  <TooltipTrigger as-child>
                    <button
                      type="button"
                      class="inline-flex size-6 items-center justify-center rounded text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700"
                      @click.stop="duplicatePane(t.id)"
                    >
                      <Icon
                        icon="fluent:copy-20-regular"
                        class="size-3.5"
                      />
                    </button>
                  </TooltipTrigger>
                  <TooltipContent side="right">
                    复制标签页
                  </TooltipContent>
                </Tooltip>
                <Tooltip :delay-duration="180">
                  <TooltipTrigger as-child>
                    <button
                      type="button"
                      class="inline-flex size-6 items-center justify-center rounded text-zinc-300 hover:bg-red-50 hover:text-red-500 disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-zinc-300"
                      :disabled="block.tabs.length <= 1"
                      @click.stop="deletePane(t.id)"
                    >
                      <Icon
                        icon="fluent:delete-20-regular"
                        class="size-3.5"
                      />
                    </button>
                  </TooltipTrigger>
                  <TooltipContent side="right">
                    {{ block.tabs.length <= 1 ? "\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6807\u7B7E\u9875" : "\u5220\u9664\u6807\u7B7E\u9875" }}
                  </TooltipContent>
                </Tooltip>
              </span>
            </div>
          </div>
        </ScrollArea>
        <Button
          variant="ghost"
          size="sm"
          class="w-full justify-start"
          @click="addPane()"
        >
          <Icon
            icon="fluent:add-20-regular"
            class="size-4"
          />
          <span>新增标签页</span>
        </Button>
      </div>

      <Separator class="my-2" />

      <!-- Layouts of the active pane -->
      <div class="flex flex-1 min-h-0 flex-col">
        <ScrollArea class="flex-1">
          <LayoutsSidebar
            v-if="activeShell"
            :model-value="activeShell.layouts.value"
            :active-index="activeShell.active.value"
            @update:model-value="(v) => {
  activeShell.layouts.value = v;
}"
            @update:active-index="(v) => {
  activeShell.active.value = v;
}"
          />
        </ScrollArea>
        <Button
          variant="ghost"
          size="sm"
          class="w-full justify-start"
          :disabled="!activeShell"
          @click="addLayoutToActive()"
        >
          <Icon
            icon="fluent:add-20-regular"
            class="size-4"
          />
          <span>新增布局</span>
        </Button>
      </div>
    </div>

    <!-- Right pane -->
    <div class="flex min-w-0 flex-1 flex-col gap-3">
      <!-- Drilled into a child block -->
      <template v-if="drilledBlockId">
        <div
          v-if="!drilledBlock"
          class="flex min-h-96 items-center justify-center p-8 text-sm text-zinc-400"
        >
          块已不存在
        </div>
        <div v-else-if="drilledEntry">
          <component
            :is="drilledEntry.config"
            :model-value="drilledBlock"
            :configure="configure"
            @update:model-value="(next) => updateDrilledBlock(next)"
          />
        </div>
        <div
          v-else
          class="p-6 text-sm text-red-500"
        >
          未注册的块类型：{{ drilledBlock.type }}@{{ drilledBlock.compatibilityDate }}
        </div>
      </template>

      <!-- General block-level settings. -->
      <template v-else-if="viewMode === 'general'">
        <Field orientation="vertical">
          <FieldLabel class="text-xs text-zinc-500">
            <template #tooltip>
              <Markdown
                source="应用在标签页最外层容器上的 CSS 样式字符串"
                block
                class="prose prose-sm prose-zinc"
              />
            </template>
            容器样式
          </FieldLabel>
          <Textarea
            :model-value="block.style ?? ''"
            placeholder="CSS 样式字符串"
            class="min-h-20 font-mono text-xs"
            @update:model-value="(v) => updateBlockStyle(String(v))"
          />
        </Field>
      </template>

      <!-- Pane editor: tab name + active layout meta strip + canvas. -->
      <template v-else-if="activePane && activeShell">
        <Field orientation="vertical">
          <FieldLabel class="text-xs text-zinc-500">
            <template
              v-if="paneFieldDescription('label')"
              #tooltip
            >
              <Markdown
                :source="paneFieldDescription('label')"
                block
                class="prose prose-sm prose-zinc"
              />
            </template>
            {{ paneFieldTitle("label") }}
          </FieldLabel>
          <LocaleField
            :model-value="activePane.label"
            :translate-hint="paneFieldTitle('label')"
            @update:model-value="(v) => updatePaneLabel(activePane.id, v)"
          />
        </Field>

        <LayoutMetaStrip
          :model-value="activeShell.layouts.value"
          :index="activeShell.active.value"
          @update:model-value="(v) => {
  activeShell.layouts.value = v;
}"
        />
        <div class="flex-1">
          <BlockLayoutEditor
            :model-value="activeShell.layouts.value"
            :active-index="activeShell.active.value"
            :selected-item-ids="activeShell.selected.value"
            :items="activeShell.editor.blocks.value"
            :find-entry="activeShell.editor.findBlockMeta"
            :get-item-label="activeShell.editor.getBlockLabel"
            :picker-entries="activeShell.editor.pickerEntries.value"
            :can-delete="true"
            item-noun="块"
            @update:model-value="(v) => {
  activeShell.layouts.value = v;
}"
            @update:active-index="(v) => {
  activeShell.active.value = v;
}"
            @update:selected-item-ids="(v) => {
  activeShell.selected.value = v;
}"
            @add-new="addBlockToActive"
            @drill-down="onDrillDown"
            @delete-item="deleteBlockFromActive"
            @delete-items="deleteBlocksFromActive"
            @copy-items="activeShell.clipboard.copy"
            @paste="activeShell.clipboard.paste"
          />
        </div>
      </template>
    </div>
  </div>
</template>

<style scoped>
.row{align-items:center;border-radius:.25rem;cursor:pointer;display:flex;font-size:.875rem;gap:.375rem;padding-block:.375rem;padding-left:.5rem;padding-right:.5rem;position:relative;transition:background-color .1s ease;-webkit-user-select:none;-moz-user-select:none;user-select:none}[data-instruction=reorder-above]:before,[data-instruction=reorder-below]:after{background:var(--primary,#2563eb);content:"";height:2px;left:.5rem;pointer-events:none;position:absolute;right:.5rem;z-index:1}[data-instruction=reorder-above]:before{top:-1px}[data-instruction=reorder-below]:after{bottom:-1px}
</style>
