<template>
  <section class="page-designer">
    <header v-if="showHeader" class="panel-header database-header designer-head">
      <div>
        <h2 class="panel-title">{{ designerCopy.title }}</h2>
        <p class="muted designer-desc">{{ designerCopy.description }}</p>
      </div>
      <div class="head-actions">
        <el-button :loading="initializing" @click="$emit('init-designs')">{{ designerCopy.initButtonLabel }}</el-button>
        <el-button type="primary" :disabled="!draft" :loading="saving" @click="saveCurrent">保存设计</el-button>
      </div>
    </header>

    <div class="page-designer-body">
      <el-empty
        v-if="!designs.length"
        class="designer-empty"
        :description="designerCopy.emptyDescription"
        :image-size="96"
      >
        <el-button type="primary" :loading="initializing" @click="$emit('init-designs')">{{ designerCopy.initButtonLabel }}</el-button>
      </el-empty>

      <div v-else class="designer-shell">
        <div class="designer-grid">
          <aside class="material-pane">
            <nav class="material-pane-tabs" aria-label="素材分类">
              <button
                v-for="tab in visibleMaterialPanelTabs"
                :key="tab.value"
                type="button"
                :class="{ active: activeMaterialPanel === tab.value }"
                @click="setActiveMaterialPanel(tab.value)"
              >
                {{ tab.label }}
              </button>
            </nav>
            <div class="material-panel-scroll">
            <div v-show="activeMaterialPanel === 'page'" class="material-panel page-material-panel">
            <div class="add-content-materials">
              <div class="matrix-head">
                <strong>添加页面</strong>
                <small>主页面、目录和子页面</small>
              </div>
              <div class="quick-actions optimized-page-actions">
                <button type="button" class="primary-page-action" @click="addPageDesign('primary')">
                  <span class="quick-action-icon"><el-icon><Plus /></el-icon></span>
                  <span class="quick-action-meta">
                    <strong>主页面</strong>
                    <small>创建独立导航入口</small>
                  </span>
                </button>
                <div class="secondary-page-actions">
                  <button type="button" @click="addPageDesign('directory')">
                    <span class="quick-action-icon"><el-icon><FolderAdd /></el-icon></span>
                    <span class="quick-action-meta">
                      <strong>菜单目录</strong>
                      <small>组织一组子页面</small>
                    </span>
                  </button>
                  <button type="button" @click="addPageDesign('secondary')">
                    <span class="quick-action-icon"><el-icon><Tickets /></el-icon></span>
                    <span class="quick-action-meta">
                      <strong>子页面</strong>
                      <small>挂到目录下面</small>
                    </span>
                  </button>
                  <button type="button" @click="selectMenuConfig">
                    <span class="quick-action-icon"><el-icon><Setting /></el-icon></span>
                    <span class="quick-action-meta">
                      <strong>菜单设置</strong>
                      <small>调整展示方式</small>
                    </span>
                  </button>
                </div>
              </div>
            </div>

            <div v-if="visibleAssociatedPages.length" class="associated-page-materials">
              <div class="matrix-head">
                <strong>关联页面</strong>
                <small>不展示在最终菜单</small>
              </div>
              <div class="associated-page-list">
                <button
                  v-for="design in visibleAssociatedPages"
                  :key="`associated-${design.designId || design.pageCode}`"
                  type="button"
                  :class="{ active: designKey(design) === selectedKey }"
                  @click="selectAssociatedPage(design)"
                >
                  <span>{{ design.pageName || design.pageCode }}</span>
                  <small>{{ design.routePath || '-' }}</small>
                </button>
              </div>
            </div>
            <div v-if="!isBusinessBlockLayout" class="material-section region-materials">
              <div class="matrix-head">
                <strong>页面结构</strong>
                <small>恢复布局区域</small>
              </div>
              <div class="palette-list">
                <button
                  v-for="item in hiddenRegions"
                  :key="`region-material-${item.id}`"
                  type="button"
                  @click="setRegionVisible(item.id, true)"
                >
                  {{ item.label }}
                </button>
                <span v-if="!hiddenRegions.length" class="material-empty">布局区域已显示</span>
              </div>
            </div>
            </div>

            <div v-show="activeMaterialPanel === 'field'" v-if="!isBusinessBlockLayout" class="material-section field-materials">
              <div class="matrix-head">
                <strong>字段池</strong>
                <small>拖入布局区域</small>
              </div>
              <div class="palette-list">
                <button
                  v-for="column in currentColumns"
                  :key="`palette-${fieldKey(column)}`"
                  type="button"
                  draggable="true"
                  @dragstart="startPaletteField(fieldKey(column))"
                >
                  {{ column.columnComment || column.javaField || column.columnName }}
                </button>
              </div>
            </div>

            <div v-show="activeMaterialPanel === 'action'" v-if="!isBusinessBlockLayout" class="material-section action-materials basic-action-materials">
              <div class="matrix-head">
                <strong>基础按钮池</strong>
                <small>页面通用操作</small>
              </div>
              <div class="palette-list action-palette">
                <button
                  v-for="action in builtinActionPalette"
                  :key="`action-palette-${action.code}`"
                  type="button"
                  draggable="true"
                  @dragstart="startPaletteAction(action.code)"
                >
                  {{ action.label }}
                </button>
              </div>
            </div>

            <div v-show="activeMaterialPanel === 'action'" v-if="!isBusinessBlockLayout" class="material-section action-materials business-action-materials">
              <div class="matrix-head">
                <strong>业务按钮池</strong>
                <small>业务流程生成的按钮</small>
              </div>
              <div class="palette-list action-palette business-action-palette">
                <button
                  v-for="action in businessActionPalette"
                  :key="`business-action-palette-${action.code}`"
                  type="button"
                  draggable="true"
                  :class="{ unmatched: !action.matchesCurrentTable }"
                  @dragstart="startPaletteAction(action.code)"
                >
                  <span>{{ action.label }}</span>
                  <small>{{ action.ownerTable || '未绑定表' }} · {{ action.method || 'POST' }}</small>
                </button>
                <span v-if="!businessActionPalette.length" class="material-empty">生成业务流程后会出现在这里</span>
              </div>
            </div>

            <div v-show="activeMaterialPanel === 'business'" v-if="!isBusinessBlockLayout && embeddedBlockDefinitions.length" class="material-section embedded-business-materials">
              <div class="matrix-head">
                <strong>业务块</strong>
                <small>拖入页面区域</small>
              </div>
              <BlockPalette
                :definitions="embeddedBlockDefinitions"
                @add-block="addEmbeddedBusinessBlock($event, 'top')"
                @drag-block="startBusinessBlockDrag"
              />
            </div>

            <div v-show="activeMaterialPanel === 'business'" v-if="isBusinessBlockLayout" class="material-section business-materials">
              <div class="matrix-head">
                <strong>业务块</strong>
                <small>点击加入页面</small>
              </div>
              <BlockPalette :definitions="blockDefinitions" @add-block="addBusinessBlock" @drag-block="startBusinessBlockDrag" />
            </div>
            </div>
          </aside>

        <main :class="['canvas', canvasMenuPositionClass]">
          <header
            :class="canvasPageMenuClass"
            @click="selectMenuConfig"
          >
            <div class="canvas-menu-brand">
              <span>{{ activeMode() === 'admin' ? 'ADM' : 'APP' }}</span>
              <strong>{{ activeMode() === 'admin' ? '后台管理' : '前台门户' }}</strong>
            </div>
            <nav class="canvas-page-menu-bar" aria-label="画布页面菜单">
              <div
                v-for="group in pageMenuGroups"
                :key="group.key"
                class="canvas-menu-group"
                :class="{
                  open: isMenuGroupOpen(group),
                  active: isMenuGroupActive(group),
                  dragging: dragging?.type === 'menuGroup' && dragging.key === group.key,
                  directory: isMenuDirectoryDesign(group.primary),
                  'has-children': group.children.length
                }"
                draggable="true"
                @dragstart.stop="startMenuGroupDrag(group)"
                @dragover.prevent
                @drop.stop="dropMenuGroup(group)"
                @dragend="clearMenuGroupDrag"
              >
                <button type="button" class="canvas-menu-parent" @click.stop="selectMenuGroup(group)">
                  {{ group.label }}
                </button>
                <button
                  v-if="group.children.length"
                  type="button"
                  class="canvas-menu-toggle"
                  :aria-expanded="isMenuGroupOpen(group)"
                  @click.stop="toggleMenuGroup(group.key)"
                >
                  <el-icon><ArrowDown /></el-icon>
                </button>
                <div v-if="group.children.length" class="canvas-menu-dropdown">
                  <button
                    v-for="child in group.children"
                    :key="child.designId || child.pageCode"
                    type="button"
                    class="canvas-menu-child"
                    :class="{ active: designKey(child) === selectedKey }"
                    @click.stop="selectMenuPage(child)"
                  >
                    <span>{{ child.pageName || child.pageCode }}</span>
                    <small>{{ child.routePath || '-' }}</small>
                  </button>
                </div>
              </div>
              <span v-if="!pageMenuGroups.length" class="canvas-menu-empty">暂无菜单页面</span>
            </nav>
          </header>

          <div class="layout-preview-head">
            <div>
              <strong>{{ currentLayoutOption.label }}</strong>
              <span>{{ currentLayoutOption.description }}</span>
            </div>
            <el-tag size="small" effect="plain">{{ pageTypeLabel }}</el-tag>
          </div>

          <BusinessBlockDesigner
            v-if="isBusinessBlockLayout"
            ref="businessBlockDesignerRef"
            :layout-json="layoutJson"
            :block-definitions="blockDefinitions"
            :tables="tables"
            :show-config="false"
            :show-palette="false"
            @select-block="handleBusinessBlockSelect"
            @update:layout-json="applyBusinessBlockLayoutJson"
          />

          <template v-else>
          <section
            class="canvas-band embedded-block-band"
            @dragover.prevent
            @drop="dropEmbeddedBusinessBlock('top')"
          >
            <div class="band-title">
              <span>顶部业务块</span>
              <small>{{ embeddedBusinessBlocks('top').length }} 个</small>
            </div>
            <div class="embedded-block-list embedded-block-grid drop-zone" :class="{ empty: !embeddedBusinessBlocks('top').length }">
              <article
                v-for="(block, index) in embeddedBusinessBlocks('top')"
                :key="`top-embedded-${block.id || index}`"
                class="embedded-block-chip"
                :class="{ active: selectedBusinessBlock?.id === block.id }"
                :style="{ gridColumn: `span ${block.span || 12}` }"
                role="button"
                tabindex="0"
                draggable="true"
                @click="selectEmbeddedBusinessBlock(block, 'top')"
                @keydown.enter.prevent="selectEmbeddedBusinessBlock(block, 'top')"
                @dragstart="startEmbeddedBlockInstanceDrag('top', index)"
                @dragover.prevent
                @drop.stop="dropEmbeddedBusinessBlock('top', index)"
              >
                <ChartBlockPreview
                  v-if="blockDefinition(block.blockCode)?.kind === 'chart'"
                  :definition="blockDefinition(block.blockCode)"
                  :block="block"
                />
                <span v-else>{{ embeddedBlockLabel(block) }}</span>
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除业务块"
                  @click.stop="removeEmbeddedBusinessBlock('top', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </article>
              <button v-if="!embeddedBusinessBlocks('top').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('business')">添加业务块</button>
            </div>
          </section>

          <section
            v-if="regionVisible('query')"
            class="canvas-band query-band"
            @dragover.prevent
            @drop="dropPaletteField('query')"
          >
            <div class="band-title">
              <span>查询区</span>
              <div class="band-tools">
                <small>{{ regionFields('query').length }} 字段</small>
                <el-button size="small" text type="danger" @click="setRegionVisible('query', false)">删除区域</el-button>
              </div>
            </div>
            <div class="chip-row drop-zone" :class="{ empty: !regionFields('query').length }">
              <button
                v-for="(field, index) in regionFields('query')"
                :key="`query-${field}`"
                type="button"
                class="field-chip"
                draggable="true"
                @dragstart="startRegionFieldDrag('query', index)"
                @dragover.prevent
                @drop.stop="dropPaletteField('query', index)"
              >
                <span>{{ fieldLabel(field) }}</span>
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除字段"
                  @click.stop="removeRegionField('query', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </button>
              <button v-if="!regionFields('query').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
            </div>
          </section>

          <section
            v-if="regionVisible('toolbar')"
            class="canvas-band toolbar-band"
            @dragover.prevent
            @drop="dropAction('toolbar')"
          >
            <div class="band-title">
              <span>工具栏按钮</span>
              <div class="band-tools">
                <small>{{ toolbarActions.length }} 按钮</small>
                <el-button size="small" text type="danger" @click="setRegionVisible('toolbar', false)">删除区域</el-button>
              </div>
            </div>
            <div class="action-row drop-zone" :class="{ empty: !toolbarActions.length }">
              <button
                v-for="(code, index) in toolbarActions"
                :key="`toolbar-${code}`"
                type="button"
                class="action-chip"
                draggable="true"
                @dragstart="startRegionActionDrag('toolbar', index)"
                @dragover.prevent
                @drop.stop="dropAction('toolbar', index)"
                @click.stop="selectActionButton('toolbar', index)"
              >
                <span>{{ actionLabel(code) }}</span>
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除按钮"
                  @click.stop="removeAction('toolbar', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </button>
              <button v-if="!toolbarActions.length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('action')">添加按钮</button>
            </div>
          </section>

          <section class="canvas-band table-band" :class="layoutPreviewClass">
            <div class="band-title">
              <span>{{ isCardLayout ? '卡片布局' : '列表布局' }}</span>
              <small>{{ activeMainFields.length }} 字段</small>
            </div>

            <div
              v-if="isCardLayout"
              class="card-canvas"
              @dragover.prevent
              @drop="dropPaletteField('card')"
            >
              <article v-for="index in 3" :key="`card-preview-${index}`" class="card-preview-item">
                <div
                  v-if="cardImageField"
                  class="card-image-slot"
                  draggable="true"
                  @dragstart="startCardSpecialFieldDrag('imageField')"
                  @dragover.prevent
                  @drop.stop="dropCardSpecialField('imageField')"
                >
                  {{ fieldLabel(cardImageField) }}
                </div>
                <div
                  v-else
                  class="card-image-slot empty"
                  @dragover.prevent
                  @drop.stop="dropCardSpecialField('imageField')"
                >
                  拖入图片字段
                </div>
                <strong
                  class="card-title-slot"
                  draggable="true"
                  @dragstart="startCardSpecialFieldDrag('titleField')"
                  @dragover.prevent
                  @drop.stop="dropCardSpecialField('titleField')"
                >
                  {{ fieldLabel(cardTitleField) || '拖入主标题字段' }}
                </strong>
                <div class="card-field-list drop-zone" :class="{ empty: !cardFields.length }">
                  <span
                    v-for="(field, fieldIndex) in cardFields"
                    :key="`card-${index}-${field}`"
                    class="card-field-chip"
                    draggable="true"
                    @dragstart="startRegionFieldDrag('card', fieldIndex)"
                    @dragover.prevent
                    @drop.stop="dropPaletteField('card', fieldIndex)"
                  >
                    <span>{{ fieldLabel(field) }}</span>
                    <span
                      class="chip-remove"
                      role="button"
                      tabindex="-1"
                      title="删除字段"
                      @click.stop="removeRegionField('card', fieldIndex)"
                      @mousedown.stop
                    >
                      <el-icon><Close /></el-icon>
                    </span>
                  </span>
                  <button v-if="!cardFields.length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
                </div>
                <div
                  class="card-actions drop-zone"
                  :class="{ empty: !cardActions.length }"
                  @dragover.prevent
                  @drop.stop="dropAction('cardActions')"
                >
                  <button
                    v-for="(code, actionIndex) in cardActions"
                    :key="`card-action-${index}-${code}`"
                    type="button"
                    draggable="true"
                    @dragstart="startRegionActionDrag('cardActions', actionIndex)"
                    @dragover.prevent
                    @drop.stop="dropAction('cardActions', actionIndex)"
                    @click.stop="selectActionButton('cardActions', actionIndex)"
                  >
                    <span>{{ actionLabel(code) }}</span>
                    <span
                      class="chip-remove"
                      role="button"
                      tabindex="-1"
                      title="删除按钮"
                      @click.stop="removeAction('cardActions', actionIndex)"
                      @mousedown.stop
                    >
                      <el-icon><Close /></el-icon>
                    </span>
                  </button>
                  <button v-if="!cardActions.length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('action')">添加按钮</button>
                </div>
              </article>
            </div>

            <div v-else-if="isSplitLayout" class="split-preview">
              <div
                class="split-sidebar drop-zone"
                @dragover.prevent
                @drop="dropPaletteField('query')"
              >
                <span v-for="field in regionFields('query').slice(0, 4)" :key="`split-${field}`">{{ fieldLabel(field) }}</span>
                <button v-if="!regionFields('query').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
              </div>
              <div
                class="table-preview drop-zone"
                @dragover.prevent
                @drop="dropPaletteField('table')"
              >
                <span
                  v-for="(field, index) in regionFields('table')"
                  :key="`table-${field}`"
                  draggable="true"
                  @dragstart="startRegionFieldDrag('table', index)"
                  @dragover.prevent
                  @drop.stop="dropPaletteField('table', index)"
                >
                  <span>{{ fieldLabel(field) }}</span>
                  <span
                    class="chip-remove"
                    role="button"
                    tabindex="-1"
                    title="删除字段"
                    @click.stop="removeRegionField('table', index)"
                    @mousedown.stop
                  >
                    <el-icon><Close /></el-icon>
                  </span>
                </span>
                <div
                  class="table-actions drop-zone"
                  :class="{ empty: !rowActions.length }"
                  @dragover.prevent
                  @drop.stop="dropAction('table')"
                >
                  <button
                    v-for="(code, actionIndex) in rowActions"
                    :key="`split-table-action-${code}-${actionIndex}`"
                    type="button"
                    draggable="true"
                    @dragstart="startRegionActionDrag('table', actionIndex)"
                    @dragover.prevent
                    @drop.stop="dropAction('table', actionIndex)"
                    @click.stop="selectActionButton('table', actionIndex)"
                  >
                    <span>{{ actionLabel(code) }}</span>
                    <span
                      class="chip-remove"
                      role="button"
                      tabindex="-1"
                      title="删除按钮"
                      @click.stop="removeAction('table', actionIndex)"
                      @mousedown.stop
                    >
                      <el-icon><Close /></el-icon>
                    </span>
                  </button>
                  <button v-if="!rowActions.length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('action')">添加按钮</button>
                </div>
                <button v-if="!regionFields('table').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
              </div>
            </div>

            <div
              v-else
              class="table-preview drop-zone"
              @dragover.prevent
              @drop="dropPaletteField('table')"
            >
              <span
                v-for="(field, index) in regionFields('table')"
                :key="`table-${field}`"
                draggable="true"
                @dragstart="startRegionFieldDrag('table', index)"
                @dragover.prevent
                @drop.stop="dropPaletteField('table', index)"
              >
                <span>{{ fieldLabel(field) }}</span>
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除字段"
                  @click.stop="removeRegionField('table', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </span>
              <div
                class="table-actions drop-zone"
                :class="{ empty: !rowActions.length }"
                @dragover.prevent
                @drop.stop="dropAction('table')"
              >
                <button
                  v-for="(code, actionIndex) in rowActions"
                  :key="`table-action-${code}-${actionIndex}`"
                  type="button"
                  draggable="true"
                  @dragstart="startRegionActionDrag('table', actionIndex)"
                  @dragover.prevent
                  @drop.stop="dropAction('table', actionIndex)"
                  @click.stop="selectActionButton('table', actionIndex)"
                >
                  <span>{{ actionLabel(code) }}</span>
                  <span
                    class="chip-remove"
                    role="button"
                    tabindex="-1"
                    title="删除按钮"
                    @click.stop="removeAction('table', actionIndex)"
                    @mousedown.stop
                  >
                    <el-icon><Close /></el-icon>
                  </span>
                </button>
                  <button v-if="!rowActions.length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('action')">添加按钮</button>
              </div>
              <button v-if="!regionFields('table').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
            </div>
          </section>

          <section
            v-if="regionVisible('form')"
            class="canvas-band"
            @dragover.prevent
            @drop="dropPaletteField('form')"
          >
            <div class="band-title">
              <span>表单字段</span>
              <div class="band-tools">
                <small>{{ regionFields('form').length }} 字段</small>
                <el-button size="small" text type="danger" @click="setRegionVisible('form', false)">删除区域</el-button>
              </div>
            </div>
            <div class="form-preview drop-zone" :class="{ empty: !regionFields('form').length }">
              <label
                v-for="(field, index) in regionFields('form')"
                :key="`form-${field}`"
                draggable="true"
                @dragstart="startRegionFieldDrag('form', index)"
                @dragover.prevent
                @drop.stop="dropPaletteField('form', index)"
              >
                <span>{{ fieldLabel(field) }}</span>
                <el-input disabled placeholder="生成后录入" />
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除字段"
                  @click.stop.prevent="removeRegionField('form', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </label>
              <button v-if="!regionFields('form').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('field')">添加字段</button>
            </div>
          </section>

          <section
            class="canvas-band embedded-block-band"
            @dragover.prevent
            @drop="dropEmbeddedBusinessBlock('bottom')"
          >
            <div class="band-title">
              <span>底部业务块</span>
              <small>{{ embeddedBusinessBlocks('bottom').length }} 个</small>
            </div>
            <div class="embedded-block-list embedded-block-grid drop-zone" :class="{ empty: !embeddedBusinessBlocks('bottom').length }">
              <article
                v-for="(block, index) in embeddedBusinessBlocks('bottom')"
                :key="`bottom-embedded-${block.id || index}`"
                class="embedded-block-chip"
                :class="{ active: selectedBusinessBlock?.id === block.id }"
                :style="{ gridColumn: `span ${block.span || 12}` }"
                role="button"
                tabindex="0"
                draggable="true"
                @click="selectEmbeddedBusinessBlock(block, 'bottom')"
                @keydown.enter.prevent="selectEmbeddedBusinessBlock(block, 'bottom')"
                @dragstart="startEmbeddedBlockInstanceDrag('bottom', index)"
                @dragover.prevent
                @drop.stop="dropEmbeddedBusinessBlock('bottom', index)"
              >
                <ChartBlockPreview
                  v-if="blockDefinition(block.blockCode)?.kind === 'chart'"
                  :definition="blockDefinition(block.blockCode)"
                  :block="block"
                />
                <span v-else>{{ embeddedBlockLabel(block) }}</span>
                <span
                  class="chip-remove"
                  role="button"
                  tabindex="-1"
                  title="删除业务块"
                  @click.stop="removeEmbeddedBusinessBlock('bottom', index)"
                  @mousedown.stop
                >
                  <el-icon><Close /></el-icon>
                </span>
              </article>
              <button v-if="!embeddedBusinessBlocks('bottom').length" type="button" class="drop-hint canvas-palette-shortcut" @click="setActiveMaterialPanel('business')">添加业务块</button>
            </div>
          </section>
          </template>
        </main>

        <aside class="property-pane" :key="propertyPaneKey">
          <div v-if="selectedConfigType === 'directory' && draft" class="canvas-menu-config">
            <div class="matrix-head">
              <strong>目录配置</strong>
              <small>只承载二级菜单</small>
            </div>
            <el-form label-position="top">
              <el-form-item label="目录名称">
                <el-input v-model="draft.pageName" @input="syncDirectoryName" />
              </el-form-item>
              <el-form-item label="目录编码">
                <el-input :model-value="directoryMenuCode" disabled />
              </el-form-item>
              <el-form-item label="二级菜单">
                <el-button type="primary" plain @click="addDirectoryChildMenu">新增二级菜单</el-button>
                <p class="layout-help">目录本身不关联页面，点击目录只编辑目录信息；页面请通过二级菜单进入。</p>
              </el-form-item>
              <el-form-item v-if="canDeletePage(draft)">
                <el-button type="danger" plain @click="deletePageDesign(draft)">删除当前目录</el-button>
              </el-form-item>
            </el-form>
            <div class="menu-config-list">
              <div
                v-for="child in selectedMenuGroup?.children || []"
                :key="`directory-child-${child.designId || child.pageCode}`"
                class="menu-config-row"
              >
                <span>{{ child.pageName || child.pageCode }}</span>
                <small>{{ child.routePath || '-' }}</small>
              </div>
            </div>
          </div>

          <div v-else-if="selectedConfigType === 'menu'" class="canvas-menu-config">
            <div class="matrix-head">
              <strong>菜单配置</strong>
              <small>一级 / 二级页面菜单</small>
            </div>
            <el-form label-position="top">
              <el-form-item label="菜单来源">
                <el-select :model-value="navigationConfig.source" @change="setNavigationConfig('source', $event)">
                  <el-option label="页面列表自动生成" value="pageDesigns" />
                  <el-option label="自定义菜单" value="custom" />
                  <el-option label="权限菜单" value="permission" />
                </el-select>
              </el-form-item>
              <el-form-item label="菜单位置">
                <el-select :model-value="navigationConfig.position" @change="setNavigationConfig('position', $event)">
                  <el-option label="画布顶部横向菜单" value="top" />
                  <el-option label="左侧竖向菜单" value="side" />
                  <el-option label="移动端折叠菜单" value="mobile" />
                </el-select>
              </el-form-item>
              <el-form-item label="二级菜单显示">
                <el-select :model-value="navigationConfig.display" @change="setNavigationConfig('display', $event)">
                  <el-option label="悬浮下拉" value="dropdown" />
                  <el-option label="直接展开" value="expanded" />
                </el-select>
              </el-form-item>
            </el-form>
            <div class="menu-config-list">
              <div v-for="group in pageMenuGroups" :key="`config-${group.key}`" class="menu-config-row">
                <span>{{ group.label }}</span>
                <small>{{ group.children.length }} 个页面</small>
              </div>
            </div>
          </div>

          <div v-else-if="selectedConfigType === 'businessButton' && selectedBusinessButtonConfig" class="canvas-menu-config business-button-config-panel">
            <div class="matrix-head">
              <strong>业务按钮配置</strong>
              <small>{{ selectedBusinessButtonAction?.ownerTable || draft?.tableName || '业务动作' }}</small>
            </div>
            <el-form label-position="top">
              <el-form-item label="按钮文案">
                <el-input
                  :model-value="selectedBusinessButtonConfig.label"
                  @input="setBusinessButtonConfig('label', $event)"
                />
              </el-form-item>
              <el-form-item label="按钮类型">
                <el-select
                  :model-value="selectedBusinessButtonConfig.type"
                  @change="setBusinessButtonConfig('type', $event)"
                >
                  <el-option label="主要" value="primary" />
                  <el-option label="成功" value="success" />
                  <el-option label="警告" value="warning" />
                  <el-option label="危险" value="danger" />
                  <el-option label="信息" value="info" />
                  <el-option label="默认" value="default" />
                </el-select>
              </el-form-item>
              <el-form-item label="图标">
                <el-input
                  :model-value="selectedBusinessButtonConfig.icon"
                  placeholder="例如 el-icon-position"
                  @input="setBusinessButtonConfig('icon', $event)"
                />
              </el-form-item>
              <el-form-item label="确认提示">
                <el-input
                  :model-value="selectedBusinessButtonConfig.confirmMessage"
                  clearable
                  placeholder="为空则直接执行"
                  @input="setBusinessButtonConfig('confirmMessage', $event)"
                />
              </el-form-item>
              <el-form-item label="成功提示">
                <el-input
                  :model-value="selectedBusinessButtonConfig.successMessage"
                  @input="setBusinessButtonConfig('successMessage', $event)"
                />
              </el-form-item>
              <el-form-item label="执行后刷新">
                <el-select
                  :model-value="selectedBusinessButtonConfig.refreshTarget"
                  @change="setBusinessButtonConfig('refreshTarget', $event)"
                >
                  <el-option label="刷新列表" value="list" />
                  <el-option label="刷新详情" value="detail" />
                  <el-option label="刷新页面" value="page" />
                  <el-option label="不刷新" value="none" />
                </el-select>
              </el-form-item>
            </el-form>
          </div>

          <el-form v-else-if="draft" label-position="top">
            <el-form-item label="页面名称">
              <el-input v-model="draft.pageName" @input="syncPageName" />
            </el-form-item>
            <el-form-item label="路由路径">
              <el-input v-model="draft.routePath" @input="markDirty" />
            </el-form-item>
            <el-form-item label="页面类型">
              <el-select v-model="draft.pageType" @change="syncLayoutForPageType">
                <el-option
                  v-for="option in pageTypeOptions"
                  :key="option.value"
                  :label="option.label"
                  :value="option.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="显示在菜单">
              <el-switch
                :key="`menu-visibility-${selectedKey}`"
                :model-value="currentShowInMenu"
                active-text="显示"
                inactive-text="隐藏"
                @change="setNavigationVisibility"
              />
              <p class="layout-help">关闭后页面仍会生成路由，可通过列表、卡片、按钮或业务块跳转进入。</p>
            </el-form-item>
            <el-form-item v-if="!currentShowInMenu" label="所属主页面">
              <el-select
                :model-value="associatedParentPageCode"
                clearable
                filterable
                placeholder="选择通过哪个主页面进入"
                @change="setAssociatedParentPage"
              >
                <el-option
                  v-for="page in associatedParentPageOptions"
                  :key="page.designId || page.pageCode"
                  :label="page.pageName || page.pageCode"
                  :value="page.pageCode"
                />
              </el-select>
              <p class="layout-help">关联后，这个隐藏页会显示在主页面左侧的关联页面里，并可用于查看按钮跳转。</p>
            </el-form-item>
            <el-form-item v-if="currentShowInMenu" label="菜单层级">
              <div :key="`menu-level-${selectedKey}-${currentMenuLevel}`" class="menu-level-switch" role="group" aria-label="菜单层级">
                <button
                  type="button"
                  :class="{ active: currentMenuLevel === 'primary' }"
                  @click="setMenuLevel('primary')"
                >
                  一级菜单
                </button>
                <button
                  type="button"
                  :class="{ active: currentMenuLevel === 'secondary' }"
                  @click="setMenuLevel('secondary')"
                >
                  二级页面
                </button>
              </div>
            </el-form-item>
            <el-form-item v-if="currentShowInMenu && currentMenuLevel === 'primary'" label="菜单显示名称">
              <el-input v-model="navigationConfig.menuName" placeholder="为空时使用页面名称" @input="syncMenuName" />
            </el-form-item>
            <el-form-item v-if="currentShowInMenu && currentMenuLevel === 'secondary'" label="所属一级菜单/目录">
              <el-select
                :key="`parent-menu-${selectedKey}-${currentParentMenuCode}`"
                ref="parentMenuSelectRef"
                :model-value="currentParentMenuCode"
                @change="setParentMenuGroup"
              >
                <el-option
                  v-for="group in parentMenuOptions"
                  :key="group.key"
                  :label="group.label"
                  :value="group.key"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="布局类型">
              <el-select :model-value="layoutCanvas" @change="selectLayout">
                <el-option
                  v-for="option in layoutOptions"
                  :key="option.value"
                  :label="option.label"
                  :value="option.value"
                />
              </el-select>
              <p class="layout-help">{{ currentLayoutOption.description }}</p>
            </el-form-item>
            <el-form-item v-if="isCardLayout" label="图片字段">
              <el-select :model-value="cardImageField" clearable @change="setCardField('imageField', $event)">
                <el-option
                  v-for="column in currentColumns"
                  :key="fieldKey(column)"
                  :label="column.columnComment || column.javaField || column.columnName"
                  :value="fieldKey(column)"
                />
              </el-select>
            </el-form-item>
            <el-form-item v-if="isCardLayout" label="主标题字段">
              <el-select :model-value="cardTitleField" clearable @change="setCardField('titleField', $event)">
                <el-option
                  v-for="column in currentColumns"
                  :key="fieldKey(column)"
                  :label="column.columnComment || column.javaField || column.columnName"
                  :value="fieldKey(column)"
                />
              </el-select>
            </el-form-item>
            <el-form-item v-if="!isBusinessBlockLayout" label="绑定数据表">
              <el-select v-model="draft.tableName" filterable clearable @change="resetLayoutForTable">
                <el-option v-for="table in tables" :key="table.tableName" :label="table.tableComment || table.tableName" :value="table.tableName" />
              </el-select>
            </el-form-item>
            <el-form-item v-if="currentShowInMenu && routeTargetPageOptions.length" label="查看跳转">
              <el-switch
                :model-value="viewRouteEnabled"
                :disabled="!viewActionAvailable"
                active-text="跳转详情页"
                inactive-text="弹窗查看"
                @change="setViewRouteEnabled"
              />
              <el-select
                class="route-target-select"
                :model-value="routeTargetPageCode"
                filterable
                placeholder="选择详情页"
                @change="setRouteTargetPage"
              >
                <el-option
                  v-for="page in routeTargetPageOptions"
                  :key="page.designId || page.pageCode"
                  :label="page.pageName || page.pageCode"
                  :value="page.pageCode"
                />
              </el-select>
              <p class="layout-help">列表或卡片的查看按钮会按详情页路由参数跳转，未开启时保持弹窗查看。</p>
            </el-form-item>
            <el-form-item v-if="canDeletePage(draft)">
              <el-button type="danger" plain @click="deletePageDesign(draft)">删除当前页面</el-button>
            </el-form-item>
          </el-form>

          <div v-if="selectedBusinessBlock || isBusinessBlockLayout" class="palette-panel business-block-config-panel">
            <div class="matrix-head">
              <strong>业务块配置</strong>
              <small>{{ selectedBusinessBlockDefinition?.name || selectedBusinessBlock?.blockCode || '请选择业务块' }}</small>
            </div>
            <ChartBlockConfigForm
              v-if="selectedBusinessBlockDefinition?.kind === 'chart'"
              :definition="selectedBusinessBlockDefinition"
              :dataset="selectedBusinessBlock?.dataset || {}"
              :display="selectedBusinessBlock?.display || {}"
              :span="selectedBusinessBlock?.span || 12"
              :tables="tables"
              :errors="selectedBusinessBlockErrors"
              @update:dataset="updateSelectedBusinessBlockDataset"
              @update:display="updateSelectedBusinessBlockDisplay"
              @update:span="updateSelectedBusinessBlockSpan"
            />
            <BlockConfigForm
              v-else
              :definition="selectedBusinessBlockDefinition"
              :model-value="selectedBusinessBlock?.config || {}"
              :tables="tables"
              @update:model-value="updateSelectedBusinessBlockConfig"
            />
          </div>

          <el-collapse>
            <el-collapse-item title="JSON">
              <el-input v-model="layoutJson" type="textarea" :rows="8" @input="markDirty" @blur="applyLayoutJson" />
              <el-input v-model="actionJson" class="json-input" type="textarea" :rows="5" @input="markDirty" @blur="applyActionJson" />
            </el-collapse-item>
          </el-collapse>
        </aside>
      </div>
      </div>
    </div>
  </section>
</template>

<script setup>
import { computed, nextTick, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ArrowDown, Close, FolderAdd, Plus, Setting, Tickets } from '@element-plus/icons-vue'
import BusinessBlockDesigner from '@/components/business-blocks/BusinessBlockDesigner.vue'
import BlockConfigForm from '@/components/business-blocks/BlockConfigForm.vue'
import BlockPalette from '@/components/business-blocks/BlockPalette.vue'
import ChartBlockConfigForm from '@/components/business-blocks/ChartBlockConfigForm.vue'
import ChartBlockPreview from '@/components/business-blocks/ChartBlockPreview.vue'
import { buildDefaultChartInstance, isChartDefinition, validateChartInstance } from '@/components/business-blocks/chartBlock.js'
import {
  applyPageMenuGroupOrder,
  associatedPagesForDesign,
  buildPageMenuGroups,
  designMatchesScope,
  isAssociatedPageType,
  isMenuDirectoryDesign,
  pageParentPageCode,
  pageGroupLabel as resolvePageGroupLabel,
  pageMenuOrder,
  shouldShowInMenu
} from '@/utils/pageDesignerMenu.js'

const props = defineProps({
  designs: { type: Array, default: () => [] },
  database: { type: Object, default: () => ({ tables: [] }) },
  blockDefinitions: { type: Array, default: () => [] },
  mode: { type: String, default: 'frontend' },
  showHeader: { type: Boolean, default: true },
  saving: Boolean,
  initializing: Boolean
})

const emit = defineEmits(['update:designs', 'init-designs', 'save-design', 'create-design', 'delete-design', 'dirty-change'])
const selectedKey = ref('')
const draft = ref(null)
const layout = ref(defaultLayout())
const actions = ref(defaultActions())
const layoutJson = ref(formatJson(defaultLayout()))
const actionJson = ref(formatJson(defaultActions()))
const suppressDirty = ref(false)
const dragging = ref(null)
const selectedBusinessBlock = ref(null)
const selectedBusinessBlockDefinition = ref(null)
const selectedBusinessBlockZone = ref('')
const businessBlockDesignerRef = ref(null)
const selectedBusinessButtonCode = ref('')
const selectedBusinessButtonRegion = ref('')
const parentMenuSelectRef = ref(null)
const pendingSelectPageCode = ref('')
const selectedConfigType = ref('page')
const activeMaterialPanel = ref('page')
const openMenuGroupKey = ref('')
const previousPageName = ref('')

const designerCopyByMode = {
  frontend: {
    title: '前台页面设计',
    description: '配置生成页面的字段、查询区、表格列、表单字段和按钮。',
    emptyDescription: '先从系统模块蓝图初始化前台页面设计',
    initButtonLabel: '初始化页面'
  },
  admin: {
    title: '后台页面设计',
    description: '配置后台管理页的查询区、工具栏、表格列、弹窗表单、批量操作和行按钮。',
    emptyDescription: '先从后台菜单和数据表初始化后台页面设计',
    initButtonLabel: '初始化后台页面'
  }
}

const layoutOptionsByPageType = {
  list: [
    { value: 'frontend-list-v1', label: '标准表格', description: '查询区、工具栏、表格和弹窗表单，适合后台维护型列表。' },
    { value: 'frontend-list-card-v1', label: '卡片列表', description: '用卡片展示记录，适合商品、内容、活动等需要更强浏览感的页面。' },
    { value: 'frontend-list-split-v1', label: '分栏列表', description: '左侧条件分组，右侧数据列表，适合分类筛选明显的页面。' }
  ],
  current_user_list: [
    { value: 'frontend-current-user-list-v1', label: '我的列表', description: '面向当前登录用户的记录列表，保留表格效率。' },
    { value: 'frontend-current-user-card-v1', label: '我的卡片', description: '面向个人中心的卡片式记录，适合订单、申请、收藏。' }
  ],
  detail: [
    { value: 'frontend-detail-v1', label: '基础详情', description: '字段分组展示，适合普通详情页。' },
    { value: 'frontend-detail-media-v1', label: '图文详情', description: '突出主图或封面，再展示详情字段。' }
  ],
  form: [
    { value: 'frontend-form-v1', label: '单页表单', description: '所有字段集中录入，适合轻量新增或申请页。' },
    { value: 'frontend-form-group-v1', label: '分组表单', description: '字段按区域分组，适合字段较多的录入页。' }
  ],
  business_block: [
    { value: 'business-blocks-v1', label: '业务块页面', description: '拖入购物车、公告、轮播等完整业务块，只配置块需要的数据表和字段。' }
  ],
  menu_directory: [
    { value: 'menu-directory-v1', label: '一级目录', description: '只作为菜单分组，不生成独立页面。' }
  ]
}

const adminLayoutOptionsByPageType = {
  list: [
    { value: 'admin-list-v1', label: '标准管理表格', description: '查询区、工具栏、数据表格和弹窗表单，适合后台 CRUD 管理。' },
    { value: 'admin-list-dense-v1', label: '高密度表格', description: '压缩字段间距并保留批量操作，适合运营高频维护页面。' },
    { value: 'admin-list-split-v1', label: '分栏管理', description: '左侧筛选或分类，右侧表格维护，适合分类层级明显的后台页面。' }
  ],
  detail: [
    { value: 'admin-detail-v1', label: '后台详情', description: '字段分组展示，适合审核、巡检和查看记录详情。' }
  ],
  form: [
    { value: 'admin-form-v1', label: '后台表单', description: '后台录入表单，适合独立新增、编辑和审核场景。' },
    { value: 'admin-form-group-v1', label: '分组表单', description: '把字段按业务区域分组，适合字段较多的后台录入页。' }
  ],
  menu_directory: [
    { value: 'menu-directory-v1', label: '一级目录', description: '只作为菜单分组，不生成独立页面。' }
  ]
}
adminLayoutOptionsByPageType.business_block = layoutOptionsByPageType.business_block
  .filter((option) => option.value === 'business-blocks-v1')

const pageTypeLabels = {
  list: '列表页',
  current_user_list: '当前用户列表',
  detail: '详情页',
  form: '表单页',
  business_block: '业务块页面',
  menu_directory: '菜单目录'
}

const pageTypeOptionsByMode = {
  frontend: [
    { label: '列表页', value: 'list' },
    { label: '详情页', value: 'detail' },
    { label: '表单页', value: 'form' },
    { label: '当前用户列表', value: 'current_user_list' },
    { label: '业务块页面', value: 'business_block' }
  ],
  admin: [
    { label: '后台列表页', value: 'list' },
    { label: '后台详情页', value: 'detail' },
    { label: '后台表单页', value: 'form' }
  ]
}

const frontendBuiltinActionPalette = [
  { code: 'create', label: '新增' },
  { code: 'batchDelete', label: '批量删除' },
  { code: 'view', label: '查看' },
  { code: 'edit', label: '编辑' },
  { code: 'delete', label: '删除' }
]

const adminBuiltinActionPalette = [
  { code: 'create', label: '新增' },
  { code: 'export', label: '导出' },
  { code: 'batchDelete', label: '批量删除' },
  { code: 'view', label: '查看' },
  { code: 'edit', label: '编辑' },
  { code: 'delete', label: '删除' }
]

const designerCopy = computed(() => designerCopyByMode[activeMode()] || designerCopyByMode.frontend)
const pageTypeOptions = computed(() => pageTypeOptionsByMode[activeMode()] || pageTypeOptionsByMode.frontend)
const builtinActionPalette = computed(() => {
  const source = activeMode() === 'admin' ? adminBuiltinActionPalette : frontendBuiltinActionPalette
  return source.filter((action) => actionAllowedForCurrentTable(action.code))
})

const businessActionPalette = computed(() => {
  const businessActions = Array.isArray(props.database?.businessActions) ? props.database.businessActions : []
  return businessActions
    .filter((action) => action?.code)
    .map((action) => ({
      code: businessActionCode(action),
      label: action.name || action.code,
      business: true,
      ownerTable: action.ownerTable || '',
      method: action.method || 'POST',
      matchesCurrentTable: businessActionMatchesCurrentTable(action)
    }))
    .sort(sortBusinessActionPalette)
})

const actionPalette = computed(() => [...builtinActionPalette.value, ...businessActionPalette.value])
const embeddedBlockDefinitions = computed(() => props.blockDefinitions.filter((definition) => isEmbeddedBlockDefinition(definition)))

const optionalRegions = [
  { id: 'query', label: '查询区' },
  { id: 'toolbar', label: '工具栏按钮' },
  { id: 'form', label: '表单字段' }
]

const materialPanelTabs = [
  { value: 'page', label: '页面' },
  { value: 'field', label: '字段' },
  { value: 'action', label: '按钮' },
  { value: 'business', label: '业务块' }
]

const embeddedBlockZones = ['top', 'bottom']

const tables = computed(() => Array.isArray(props.database?.tables) ? props.database.tables : [])
const selectedBusinessBlockErrors = computed(() => (
  selectedBusinessBlockDefinition.value?.kind === 'chart'
    ? validateChartInstance(selectedBusinessBlockDefinition.value, selectedBusinessBlock.value, tables.value)
    : []
))
const currentTable = computed(() => tables.value.find((table) => table.tableName === draft.value?.tableName) || null)
const currentColumns = computed(() => Array.isArray(currentTable.value?.columns) ? currentTable.value.columns : [])
const toolbarActions = computed(() => regionActions('toolbar', 'actions'))
const rowActions = computed(() => regionActions('table', 'rowActions'))
const cardActions = computed(() => regionActions('cardActions', 'actions'))
const currentPageType = computed(() => draft.value?.pageType || 'list')
const pageTypeLabel = computed(() => pageTypeLabels[currentPageType.value] || currentPageType.value)
const layoutOptions = computed(() => layoutOptionsForPageType(currentPageType.value))
const layoutCanvas = computed(() => normalizeLayoutCanvas(layout.value?.canvas, currentPageType.value))
const isBusinessBlockLayout = computed(() => layoutCanvas.value === 'business-blocks-v1' || draft.value?.pageType === 'business_block')
const visibleMaterialPanelTabs = computed(() => {
  return materialPanelTabs.filter((tab) => {
    if (isBusinessBlockLayout.value) {
      return tab.value === 'page' || tab.value === 'business'
    }
    if (tab.value === 'business') {
      return embeddedBlockDefinitions.value.length || props.blockDefinitions.length
    }
    return true
  })
})
const currentLayoutOption = computed(() => layoutOptions.value.find((option) => option.value === layoutCanvas.value) || layoutOptions.value[0])
const isCardLayout = computed(() => layoutCanvas.value.includes('card'))
const isSplitLayout = computed(() => layoutCanvas.value.includes('split'))
const activeMainRegionId = computed(() => isCardLayout.value ? 'card' : 'table')
const activeMainFields = computed(() => regionFields(activeMainRegionId.value))
const cardFields = computed(() => regionFields('card'))
const cardImageField = computed(() => cardRegion().imageField || '')
const cardTitleField = computed(() => cardRegion().titleField || '')
const hiddenRegions = computed(() => optionalRegions.filter((item) => !regionVisible(item.id)))
const layoutPreviewClass = computed(() => ({
  'layout-preview-card': isCardLayout.value,
  'layout-preview-split': isSplitLayout.value
}))
const propertyPaneKey = computed(() => `${selectedConfigType.value}:${selectedKey.value || 'none'}`)
const selectedBusinessButtonConfig = computed(() => selectedBusinessButtonCode.value
  ? businessButtonConfig(selectedBusinessButtonCode.value, false)
  : null)
const selectedBusinessButtonAction = computed(() => businessActionForCode(selectedBusinessButtonCode.value))
const navigationConfig = computed(() => ensureNavigationConfig())
const menuPositionOptions = ['top', 'side', 'mobile']
const canvasMenuPosition = computed(() => {
  const position = navigationConfig.value.position
  return menuPositionOptions.includes(position) ? position : 'top'
})
const canvasMenuPositionClass = computed(() => `menu-position-${canvasMenuPosition.value}`)
const canvasPageMenuClass = computed(() => [
  'canvas-page-menu',
  canvasMenuPositionClass.value,
  {
    active: selectedConfigType.value === 'menu',
    'menu-display-expanded': navigationConfig.value.display === 'expanded'
  }
])
const scopedPageMenuDesigns = computed(() => props.designs.filter((design) => designMatchesScope(design, activeMode())))
const pageMenuDesigns = computed(() => {
  const sourceDesigns = scopedPageMenuDesigns.value
  if (!draft.value || !selectedKey.value) return sourceDesigns
  const current = {
    ...draft.value,
    layoutJson: formatJson(layout.value)
  }
  const hasSelected = sourceDesigns.some((design) => designKey(design) === selectedKey.value)
  const designs = hasSelected
    ? sourceDesigns.map((design) => designKey(design) === selectedKey.value ? current : design)
    : sourceDesigns.concat(current)
  return designs
})
const editableMenuPages = computed(() => pageMenuDesigns.value.filter(shouldShowInMenu))
const pageMenuGroups = computed(() => buildPageMenuGroups(editableMenuPages.value, { mode: activeMode() }))
const selectedMenuGroup = computed(() => pageMenuGroups.value.find((group) => {
  return designKey(group.primary) === selectedKey.value || group.children.some((child) => designKey(child) === selectedKey.value)
}) || null)
const currentDraftDesign = computed(() => {
  if (!draft.value) return null
  return {
    ...draft.value,
    layoutJson: formatJson(layout.value)
  }
})
const selectedPageAssociatedPages = computed(() => {
  return currentDraftDesign.value ? associatedPagesForDesign(pageMenuDesigns.value, currentDraftDesign.value) : []
})
const unlinkedAssociatedPages = computed(() => {
  return pageMenuDesigns.value.filter((design) => {
    return !shouldShowInMenu(design) && !pageParentPageCode(design) && designKey(design) !== selectedKey.value
  })
})
const visibleAssociatedPages = computed(() => {
  if (selectedConfigType.value !== 'page') return []
  return uniqueDesigns(selectedPageAssociatedPages.value.concat(unlinkedAssociatedPages.value))
})
const associatedParentPageCode = computed(() => pageParentPageCode(currentDraftDesign.value))
const associatedParentPageOptions = computed(() => {
  return pageMenuDesigns.value.filter((design) => {
    return shouldShowInMenu(design) && !isMenuDirectoryDesign(design) && designKey(design) !== selectedKey.value
  })
})
const routeTargetPageOptions = computed(() => selectedPageAssociatedPages.value)
const routeTargetPageCode = computed(() => {
  return currentActionInteraction('table:view')?.targetPageCode
    || currentActionInteraction('cardActions:view')?.targetPageCode
    || currentInteractionsConfig().defaultDetailPageCode
    || routeTargetPageOptions.value[0]?.pageCode
    || ''
})
const viewRouteEnabled = computed(() => {
  return currentActionInteraction('table:view')?.type === 'route'
    || currentActionInteraction('cardActions:view')?.type === 'route'
})
const viewActionAvailable = computed(() => {
  return rowActions.value.includes('view') || rowActions.value.includes('detail')
    || cardActions.value.includes('view') || cardActions.value.includes('detail')
})
const parentMenuOptions = computed(() => {
  return pageMenuGroups.value.filter((group) => {
    return group.primary && isMenuDirectoryDesign(group.primary) && designKey(group.primary) !== selectedKey.value
  })
})

function inferCurrentParentMenuCode() {
  if (!draft.value) return ''
  const config = currentNavigationConfig()
  const explicitParent = config.parentMenuCode || draft.value?.menuParentCode || draft.value?.parentMenuCode
  if (explicitParent) return String(explicitParent)
  const menuCode = config.menuCode || draft.value?.menuCode
  if (!menuCode || String(menuCode) === String(draft.value?.pageCode || '')) return ''
  const parent = parentMenuOptions.value.find((group) => String(group.key) === String(menuCode))
  return parent ? parent.key : ''
}

function currentMenuPanelState() {
  if (!draft.value) {
    return { showInMenu: false, level: 'primary', parentCode: '' }
  }
  const config = currentNavigationConfig()
  const isDirectory = draft.value?.pageType === 'menu_directory' || config.nodeType === 'directory' || config.menuType === 'directory'
  const parentCode = isDirectory ? '' : inferCurrentParentMenuCode()
  const level = isDirectory || !parentCode
    ? (config.menuLevel === 'secondary' ? 'secondary' : 'primary')
    : 'secondary'
  const showInMenu = typeof config.showInMenu === 'boolean'
    ? config.showInMenu
    : !isAssociatedPageType(draft.value?.pageType)
  return { showInMenu, level, parentCode }
}

const menuPanelState = computed(() => currentMenuPanelState())
const currentShowInMenu = computed(() => menuPanelState.value.showInMenu)
const currentMenuLevel = computed(() => {
  return menuPanelState.value.level
})
const currentParentMenuCode = computed(() => {
  return menuPanelState.value.parentCode
})
const directoryMenuCode = computed(() => {
  if (!draft.value) return ''
  const config = ensureNavigationConfig()
  return config.menuCode || draft.value.menuCode || draft.value.pageCode || ''
})

watch(
  () => scopedPageMenuDesigns.value,
  () => {
    const designs = scopedPageMenuDesigns.value
    if (!designs.length) {
      selectedKey.value = ''
      draft.value = null
      return
    }
    const pending = pendingSelectPageCode.value
      ? designs.find((item) => item?.pageCode === pendingSelectPageCode.value)
      : null
    if (pending) {
      pendingSelectPageCode.value = ''
    }
    const selected = pending || designs.find((item) => designKey(item) === selectedKey.value) || designs[0]
    selectDesign(selected, false)
  },
  { immediate: true, deep: true }
)

watch(
  () => visibleMaterialPanelTabs.value.map((tab) => tab.value),
  (values) => {
    if (!values.includes(activeMaterialPanel.value)) {
      activeMaterialPanel.value = values[0] || 'page'
    }
  },
  { immediate: true }
)

function designKey(design) {
  return String(design?.designId || design?.pageCode || '')
}

function uniqueDesigns(designs) {
  const seen = new Set()
  return (Array.isArray(designs) ? designs : []).filter((design) => {
    const key = designKey(design)
    if (!key || seen.has(key)) return false
    seen.add(key)
    return true
  })
}

function activeMode() {
  return props.mode === 'admin' ? 'admin' : 'frontend'
}

function setActiveMaterialPanel(value) {
  if (visibleMaterialPanelTabs.value.some((tab) => tab.value === value)) {
    activeMaterialPanel.value = value
  }
}

function pageGroupLabel(design) {
  return resolvePageGroupLabel(design, activeMode())
}

function isMenuGroupOpen(group) {
  return openMenuGroupKey.value === group.key
}

function isMenuGroupActive(group) {
  return designKey(group.primary) === selectedKey.value || group.children.some((child) => designKey(child) === selectedKey.value)
}

function toggleMenuGroup(key) {
  openMenuGroupKey.value = openMenuGroupKey.value === key ? '' : key
}

function selectMenuGroup(group) {
  if (group?.primary) {
    openMenuGroupKey.value = ''
    selectDesign(group.primary)
    return
  }
  if (group?.children?.length) {
    toggleMenuGroup(group.key)
    selectMenuConfig()
    return
  }
  toggleMenuGroup(group.key)
  selectMenuConfig()
}

function selectMenuPage(design) {
  openMenuGroupKey.value = ''
  selectedConfigType.value = 'page'
  clearBusinessButtonSelection()
  selectDesign(design)
}

function selectAssociatedPage(design) {
  selectedConfigType.value = 'page'
  clearBusinessButtonSelection()
  selectDesign(design)
}

function selectMenuConfig() {
  selectedConfigType.value = 'menu'
  clearBusinessButtonSelection()
  handleBusinessBlockSelect(null)
}

function selectDesign(design, shouldCommit = true) {
  const nextKey = designKey(design)
  if (shouldCommit && draft.value && selectedKey.value && nextKey !== selectedKey.value) {
    commitCurrentDraft()
  }
  suppressDirty.value = true
  try {
    selectedKey.value = nextKey
    draft.value = JSON.parse(JSON.stringify(design || {}))
    previousPageName.value = draft.value?.pageName || ''
    selectedConfigType.value = isMenuDirectoryDesign(draft.value) ? 'directory' : 'page'
    clearBusinessButtonSelection()
    handleBusinessBlockSelect(null)
    layout.value = parseJson(draft.value.layoutJson, defaultLayout(draft.value?.pageType || 'list'))
    actions.value = parseJson(draft.value.actionJson, defaultActions())
    syncLayoutForPageType()
    normalizeCurrentMenuConfig()
    syncJson({ silent: true })
  } finally {
    suppressDirty.value = false
  }
}

function addPageDesign(menuLevel = 'primary') {
  const isDirectory = menuLevel === 'directory'
  const isSecondary = menuLevel === 'secondary'
  const tableName = draft.value?.tableName || tables.value[0]?.tableName || ''
  if (!isDirectory && !tableName) {
    ElMessage.warning('请先配置至少一张数据表')
    return
  }
  commitCurrentDraft()
  const pageNumber = props.designs.length + 1
  const pageCode = uniquePageCode(isDirectory ? `menu_dir_${pageNumber}` : `custom_page_${pageNumber}`)
  const parentGroup = isSecondary ? defaultParentMenuGroup() : null
  if (isSecondary && !parentGroup) {
    ElMessage.warning('请先创建一级菜单或目录')
    return
  }
  const pageName = isDirectory ? `新一级目录${pageNumber}` : isSecondary ? `新二级菜单${pageNumber}` : `新一级菜单${pageNumber}`
  const layoutPayload = newPageLayoutJson(isDirectory ? 'directory' : isSecondary ? 'secondary' : 'primary', pageCode, pageName, parentGroup)
  pendingSelectPageCode.value = pageCode
  emit('create-design', {
    pageScope: activeMode(),
    pageCode,
    menuCode: isSecondary && parentGroup ? parentGroup.key : pageCode,
    pageName,
    routePath: isDirectory ? '' : `/${pageCode.replace(/_/g, '-')}`,
    pageType: isDirectory ? 'menu_directory' : 'list',
    tableName: isDirectory ? '' : tableName,
    layoutJson: layoutPayload,
    status: '0'
  })
}

function addDirectoryChildMenu() {
  if (!selectedMenuGroup.value) return
  addPageDesign('secondary')
}

function defaultParentMenuGroup() {
  const directoryGroups = pageMenuGroups.value.filter((group) => group.primary && isMenuDirectoryDesign(group.primary))
  const selectedGroup = directoryGroups.find((group) => isMenuGroupActive(group))
  return selectedGroup || directoryGroups[0] || null
}

function nextTopLevelMenuOrder() {
  const orders = pageMenuGroups.value
    .map((group) => Number(group.order))
    .filter((order) => Number.isFinite(order) && order < Number.MAX_SAFE_INTEGER)
  return orders.length ? Math.max(...orders) + 1 : pageMenuGroups.value.length + 1
}

function nextChildMenuOrder(parentGroup) {
  const orders = (parentGroup?.children || [])
    .map((design) => pageMenuOrder(design))
    .filter((order) => Number.isFinite(order) && order < Number.MAX_SAFE_INTEGER)
  return orders.length ? Math.max(...orders) + 1 : (parentGroup?.children?.length || 0) + 1
}

function newPageLayoutJson(menuLevel, pageCode, pageName, parentGroup) {
  const nextLayout = defaultLayout(menuLevel === 'directory' ? 'menu_directory' : 'list')
  nextLayout.navigation = {
    ...defaultNavigationConfig('list'),
    showInMenu: true,
    menuLevel: menuLevel === 'directory' ? 'primary' : menuLevel,
    menuOrder: nextTopLevelMenuOrder()
  }
  if (menuLevel === 'directory') {
    nextLayout.navigation.nodeType = 'directory'
    nextLayout.navigation.menuCode = pageCode
    nextLayout.navigation.menuName = pageName
  } else if (menuLevel === 'primary') {
    nextLayout.navigation.menuCode = pageCode
    nextLayout.navigation.menuName = pageName
  } else if (parentGroup) {
    nextLayout.navigation.parentMenuCode = parentGroup.key
    nextLayout.navigation.parentMenuName = parentGroup.label
    nextLayout.navigation.menuOrder = nextChildMenuOrder(parentGroup)
  }
  return formatJson(nextLayout)
}

function startMenuGroupDrag(group) {
  if (!group?.key) return
  dragging.value = { type: 'menuGroup', key: group.key }
}

function clearMenuGroupDrag() {
  if (dragging.value?.type === 'menuGroup') {
    dragging.value = null
  }
}

function dropMenuGroup(targetGroup) {
  if (dragging.value?.type !== 'menuGroup' || !targetGroup?.key) return
  const sourceKey = dragging.value.key
  dragging.value = null
  if (!sourceKey || sourceKey === targetGroup.key) return
  const groups = pageMenuGroups.value.slice()
  const sourceIndex = groups.findIndex((group) => group.key === sourceKey)
  const targetIndex = groups.findIndex((group) => group.key === targetGroup.key)
  if (sourceIndex < 0 || targetIndex < 0) return
  const [moved] = groups.splice(sourceIndex, 1)
  groups.splice(targetIndex, 0, moved)
  applyMenuGroupOrder(groups)
}

function applyMenuGroupOrder(groups) {
  const selected = selectedKey.value
  const nextDesigns = applyPageMenuGroupOrder(pageMenuDesigns.value, groups, { mode: activeMode() })
  const nextCurrent = nextDesigns.find((design) => designKey(design) === selected)
  if (nextCurrent && draft.value) {
    draft.value = JSON.parse(JSON.stringify(nextCurrent))
    layout.value = parseJson(draft.value.layoutJson, defaultLayout(draft.value?.pageType || 'list'))
    syncJson({ silent: true })
  }
  emit('update:designs', nextDesigns)
  emit('dirty-change', true)
}

function deletePageDesign(design) {
  if (!design?.designId) return
  emit('delete-design', design)
}

function canDeletePage(design) {
  const pageCode = String(design?.pageCode || '')
  return pageCode.startsWith('custom_page_') || pageCode.startsWith('menu_dir_')
}

function uniquePageCode(baseCode) {
  const usedCodes = new Set(props.designs.map((item) => item?.pageCode).filter(Boolean))
  let code = baseCode
  let index = 1
  while (usedCodes.has(code)) {
    index += 1
    code = `${baseCode}_${index}`
  }
  return code
}

async function addBusinessBlock(definition) {
  if (!definition || !draft.value) return
  if (!isBusinessBlockLayout.value) {
    draft.value.pageType = 'business_block'
    layout.value = { canvas: 'business-blocks-v1', blocks: [] }
    handleBusinessBlockSelect(null)
    syncJson()
    await nextTick()
  }
  businessBlockDesignerRef.value?.addBlock?.(definition)
}

function addEmbeddedBusinessBlock(definition, zone = 'top') {
  if (!definition || !draft.value) return
  if (!isEmbeddedBlockDefinition(definition)) {
    ElMessage.warning('该业务块只能放在业务块页面')
    return
  }
  ensureEmbeddedBlocks()
  const blocks = embeddedBusinessBlocks(zone)
  const id = nextBusinessBlockId(definition.code)
  const instance = isChartDefinition(definition)
    ? buildDefaultChartInstance(definition, id)
    : {
        id,
        blockCode: definition.code,
        name: definition.name || definition.code,
        config: {}
      }
  blocks.push(instance)
  selectEmbeddedBusinessBlock(instance, zone)
  syncJson()
}

function startBusinessBlockDrag(definition) {
  if (!definition) return
  dragging.value = { type: 'businessBlock', definition }
}

function startEmbeddedBlockInstanceDrag(zone, index) {
  const blocks = embeddedBusinessBlocks(zone)
  dragging.value = { type: 'embeddedBusinessBlock', sourceZone: zone, index, block: blocks[index] }
}

function dropEmbeddedBusinessBlock(zone, index) {
  if (!dragging.value) return
  ensureEmbeddedBlocks()
  if (dragging.value.type === 'businessBlock') {
    addEmbeddedBusinessBlock(dragging.value.definition, zone)
    dragging.value = null
    return
  }
  if (dragging.value.type !== 'embeddedBusinessBlock' || !dragging.value.block) return
  const sourceBlocks = embeddedBusinessBlocks(dragging.value.sourceZone)
  const targetBlocks = embeddedBusinessBlocks(zone)
  const [moved] = sourceBlocks.splice(dragging.value.index, 1)
  if (!moved) {
    dragging.value = null
    return
  }
  let insertAt = Number.isInteger(index) ? index : targetBlocks.length
  if (dragging.value.sourceZone === zone && dragging.value.index < insertAt) {
    insertAt -= 1
  }
  targetBlocks.splice(Math.max(0, insertAt), 0, moved)
  selectEmbeddedBusinessBlock(moved, zone)
  dragging.value = null
  syncJson()
}

function removeEmbeddedBusinessBlock(zone, index) {
  const blocks = embeddedBusinessBlocks(zone)
  if (!Number.isInteger(index) || index < 0 || index >= blocks.length) return
  const [removed] = blocks.splice(index, 1)
  if (removed?.id && selectedBusinessBlock.value?.id === removed.id) {
    handleBusinessBlockSelect(null)
  }
  syncJson()
}

function selectEmbeddedBusinessBlock(block, zone) {
  selectedBusinessBlock.value = block || null
  selectedBusinessBlockDefinition.value = blockDefinition(block?.blockCode)
  selectedBusinessBlockZone.value = block ? zone : ''
  clearBusinessButtonSelection()
  if (block) {
    selectedConfigType.value = 'businessBlock'
  }
}

function embeddedBusinessBlocks(zone) {
  ensureEmbeddedBlocks()
  return layout.value.embeddedBlocks[zone] || []
}

function embeddedBlockLabel(block) {
  const definition = blockDefinition(block?.blockCode)
  return block?.name || definition?.name || block?.blockCode || '业务块'
}

function blockDefinition(code) {
  return props.blockDefinitions.find((definition) => definition?.code === code) || null
}

function isEmbeddedBlockDefinition(definition) {
  return definition?.placement === 'embedded' || definition?.placements?.includes('embedded')
}

function nextBusinessBlockId(code) {
  const blocks = []
  if (Array.isArray(layout.value?.blocks)) {
    blocks.push(...layout.value.blocks)
  }
  ensureEmbeddedBlocks()
  embeddedBlockZones.forEach((zone) => blocks.push(...embeddedBusinessBlocks(zone)))
  const count = blocks.filter((block) => block?.blockCode === code).length + 1
  return `${code}_${String(count).padStart(3, '0')}`
}

function defaultLayout(pageType = 'list') {
  return {
    canvas: pageType === 'menu_directory' ? 'menu-directory-v1' : pageType === 'business_block' ? 'business-blocks-v1' : activeMode() === 'admin' ? 'admin-list-v1' : 'frontend-list-v1',
    navigation: defaultNavigationConfig(pageType),
    regions: [
      { id: 'query', type: 'query', visible: true, fields: [] },
      { id: 'toolbar', type: 'toolbar', visible: true, actions: ['create'] },
      { id: 'table', type: 'table', fields: [], rowActions: ['edit', 'delete'] },
      { id: 'form', type: 'dialogForm', visible: true, fields: [] }
    ],
    embeddedBlocks: { top: [], bottom: [] }
  }
}

function defaultNavigationConfig(pageType = currentPageType.value) {
  return {
    source: 'pageDesigns',
    position: 'top',
    display: 'dropdown',
    showInMenu: !isAssociatedPageType(pageType)
  }
}

function ensureNavigationConfig() {
  if (!layout.value || typeof layout.value !== 'object') {
    layout.value = defaultLayout()
  }
  if (!layout.value.navigation || typeof layout.value.navigation !== 'object' || Array.isArray(layout.value.navigation)) {
    layout.value.navigation = defaultNavigationConfig(currentPageType.value)
  }
  layout.value.navigation = {
    ...defaultNavigationConfig(currentPageType.value),
    ...layout.value.navigation
  }
  return layout.value.navigation
}

function currentNavigationConfig() {
  const config = layout.value?.navigation
  return config && typeof config === 'object' && !Array.isArray(config) ? config : {}
}

function currentInteractionsConfig() {
  const config = layout.value?.interactions
  return config && typeof config === 'object' && !Array.isArray(config) ? config : {}
}

function ensureInteractionsConfig() {
  if (!layout.value || typeof layout.value !== 'object') {
    layout.value = defaultLayout()
  }
  if (!layout.value.interactions || typeof layout.value.interactions !== 'object' || Array.isArray(layout.value.interactions)) {
    layout.value.interactions = {}
  }
  if (!layout.value.interactions.actions || typeof layout.value.interactions.actions !== 'object' || Array.isArray(layout.value.interactions.actions)) {
    layout.value.interactions.actions = {}
  }
  return layout.value.interactions
}

function currentActionInteraction(key) {
  const actions = currentInteractionsConfig().actions
  return actions && typeof actions === 'object' && !Array.isArray(actions) ? actions[key] || null : null
}

function setNavigationConfig(key, value) {
  const config = ensureNavigationConfig()
  config[key] = value
  syncJson()
}

function syncDirectoryName() {
  if (!draft.value) return
  const config = ensureNavigationConfig()
  config.showInMenu = true
  config.menuLevel = 'primary'
  config.nodeType = 'directory'
  config.menuCode = config.menuCode || draft.value.menuCode || draft.value.pageCode
  config.menuName = draft.value.pageName
  delete config.menuNameManual
  draft.value.menuCode = config.menuCode
  previousPageName.value = draft.value.pageName || ''
  syncJson()
}

function syncPageName(value) {
  if (!draft.value) return
  const nextName = value ?? draft.value.pageName ?? ''
  const config = ensureNavigationConfig()
  if (shouldSyncMenuNameWithPageName(config)) {
    config.menuName = nextName
    delete config.menuNameManual
  }
  previousPageName.value = nextName
  syncJson()
}

function syncMenuName() {
  const config = ensureNavigationConfig()
  const menuName = String(config.menuName || '')
  const pageName = String(draft.value?.pageName || '')
  if (menuName && menuName !== pageName) {
    config.menuNameManual = true
  } else {
    delete config.menuNameManual
  }
  syncJson()
}

function setNavigationVisibility(value) {
  const config = ensureNavigationConfig()
  config.showInMenu = Boolean(value)
  if (config.showInMenu) {
    delete config.parentPageCode
    normalizeCurrentMenuConfig()
  }
  syncJson()
}

function setAssociatedParentPage(value) {
  const config = ensureNavigationConfig()
  if (value) {
    config.parentPageCode = value
  } else {
    delete config.parentPageCode
  }
  syncJson()
}

function shouldSyncMenuNameWithPageName(config) {
  if (!draft.value || !config.showInMenu || currentMenuLevel.value !== 'primary' || config.menuNameManual) {
    return false
  }
  const menuName = String(config.menuName || '')
  const previousName = String(previousPageName.value || '')
  return !menuName || menuName === previousName || isGeneratedDefaultMenuName(menuName)
}

function isGeneratedDefaultMenuName(menuName) {
  const value = String(menuName || '')
  const code = String(draft.value?.pageCode || '')
  const match = code.match(/(?:custom_page|menu_dir)_(\d+)$/)
  if (!match) return false
  const number = match[1]
  return value === `新一级菜单${number}` || value === `新二级菜单${number}` || value === `新一级目录${number}`
}

function findParentMenuOption(key) {
  if (!key) return null
  return parentMenuOptions.value.find((group) => String(group.key) === String(key)) || null
}

function applySecondaryParent(parent) {
  if (!draft.value || !parent) return
  const config = ensureNavigationConfig()
  config.menuLevel = 'secondary'
  config.parentMenuCode = parent.key
  config.parentMenuName = parent.label
  delete config.menuName
  draft.value.menuCode = parent.key
}

function applyPrimaryMenuState() {
  if (!draft.value) return
  const config = ensureNavigationConfig()
  config.menuLevel = 'primary'
  config.menuCode = draft.value.pageCode || config.menuCode
  config.menuName = draft.value.pageName || config.menuName
  delete config.menuNameManual
  delete config.parentMenuCode
  delete config.parentMenuName
  delete draft.value.menuParentCode
  delete draft.value.menuParentName
  delete draft.value.parentMenuCode
  delete draft.value.parentMenuName
  if (draft.value.pageCode) {
    draft.value.menuCode = draft.value.pageCode
  }
}

function normalizeCurrentMenuConfig() {
  if (!draft.value) return
  const config = ensureNavigationConfig()
  if (draft.value.pageType === 'menu_directory' || config.nodeType === 'directory' || config.menuType === 'directory') {
    config.showInMenu = true
    config.menuLevel = 'primary'
    config.nodeType = 'directory'
    config.menuCode = config.menuCode || draft.value.menuCode || draft.value.pageCode
    config.menuName = draft.value.pageName || config.menuName
    draft.value.menuCode = config.menuCode
    return
  }
  if (config.showInMenu === false) return
  const inferredParent = findParentMenuOption(inferCurrentParentMenuCode())
  if (inferredParent) {
    applySecondaryParent(inferredParent)
    return
  }
  if (config.menuLevel === 'secondary' && parentMenuOptions.value[0]) {
    applySecondaryParent(parentMenuOptions.value[0])
    return
  }
  if (!config.menuLevel) {
    applyPrimaryMenuState()
  }
}

function setMenuLevel(value) {
  const level = value === 'secondary' ? 'secondary' : 'primary'
  const config = ensureNavigationConfig()
  config.menuLevel = level
  if (level === 'primary') {
    applyPrimaryMenuState()
  } else {
    const parent = findParentMenuOption(inferCurrentParentMenuCode()) || parentMenuOptions.value[0]
    applySecondaryParent(parent)
  }
  syncJson()
}

function setParentMenuGroup(key) {
  const parent = findParentMenuOption(key)
  if (!parent) return
  applySecondaryParent(parent)
  syncJson()
  nextTick(() => {
    parentMenuSelectRef.value?.blur?.()
  })
}

function setRouteTargetPage(pageCode) {
  const interactions = ensureInteractionsConfig()
  if (pageCode) {
    interactions.defaultDetailPageCode = pageCode
  } else {
    delete interactions.defaultDetailPageCode
  }
  if (viewRouteEnabled.value) {
    if (pageCode) {
      applyViewRouteInteraction(pageCode)
    } else {
      clearViewRouteInteraction()
    }
  }
  syncJson()
}

function setViewRouteEnabled(value) {
  if (!value) {
    clearViewRouteInteraction()
    syncJson()
    return
  }
  const pageCode = routeTargetPageCode.value || routeTargetPageOptions.value[0]?.pageCode || ''
  if (!pageCode) {
    ElMessage.warning('请先关联一个详情页')
    return
  }
  ensureInteractionsConfig().defaultDetailPageCode = pageCode
  applyViewRouteInteraction(pageCode)
  syncJson()
}

function applyViewRouteInteraction(pageCode) {
  const targetPage = routeTargetPageOptions.value.find((page) => page.pageCode === pageCode)
  const interactions = ensureInteractionsConfig()
  interactions.actions['table:view'] = routeInteractionConfig(pageCode, targetPage)
  interactions.actions['cardActions:view'] = routeInteractionConfig(pageCode, targetPage)
}

function clearViewRouteInteraction() {
  const interactions = ensureInteractionsConfig()
  delete interactions.actions['table:view']
  delete interactions.actions['cardActions:view']
}

function routeInteractionConfig(pageCode, targetPage) {
  return {
    type: 'route',
    targetPageCode: pageCode,
    paramMap: defaultRouteParamMap(targetPage),
    fallback: 'dialog'
  }
}

function defaultRouteParamMap(targetPage) {
  const params = routeParamNames(targetPage?.routePath)
  const primaryField = primaryRouteField()
  const result = {}
  params.forEach((param) => {
    const matchedField = currentColumns.value
      .map(fieldKey)
      .find((field) => field === param)
    result[param] = matchedField || (param === 'id' && primaryField ? primaryField : primaryField || param)
  })
  return result
}

function primaryRouteField() {
  const primaryColumn = currentColumns.value.find((column) => {
    return column?.isPk === '1' || column?.isPk === true || column?.columnKey === 'PRI'
  })
  return fieldKey(primaryColumn) || fieldKey(currentColumns.value[0]) || 'id'
}

function routeParamNames(routePath) {
  const result = []
  String(routePath || '').replace(/:([A-Za-z0-9_]+)/g, (_, name) => {
    result.push(name)
    return ''
  })
  return result
}

function layoutOptionsForPageType(pageType) {
  const optionsByPageType = activeMode() === 'admin' ? adminLayoutOptionsByPageType : layoutOptionsByPageType
  return optionsByPageType[pageType] || optionsByPageType.list
}

function defaultCanvasForPageType(pageType) {
  return layoutOptionsForPageType(pageType)[0].value
}

function normalizeLayoutCanvas(canvas, pageType) {
  const options = layoutOptionsForPageType(pageType)
  return options.some((option) => option.value === canvas) ? canvas : options[0].value
}

function syncLayoutForPageType() {
  if (!layout.value || typeof layout.value !== 'object') {
    layout.value = defaultLayout()
  }
  layout.value.canvas = normalizeLayoutCanvas(layout.value.canvas, currentPageType.value)
  ensureNavigationConfig()
  if (isMenuDirectoryDesign(draft.value)) {
    syncDirectoryName()
    return
  }
  if (layout.value.canvas === 'business-blocks-v1') {
    if (!Array.isArray(layout.value.blocks)) {
      layout.value.blocks = []
    }
    syncJson()
    return
  }
  ensureEmbeddedBlocks()
  ensureLayoutRegions()
  syncJson()
}

function selectLayout(value) {
  const options = layoutOptionsForPageType(currentPageType.value)
  layout.value.canvas = options.some((option) => option.value === value) ? value : defaultCanvasForPageType(currentPageType.value)
  ensureNavigationConfig()
  if (layout.value.canvas === 'business-blocks-v1') {
    if (!Array.isArray(layout.value.blocks)) {
      layout.value.blocks = []
    }
    syncJson()
    return
  }
  ensureEmbeddedBlocks()
  ensureLayoutRegions()
  syncJson()
}

function ensureEmbeddedBlocks() {
  if (!layout.value || typeof layout.value !== 'object') {
    layout.value = defaultLayout()
  }
  if (!layout.value.embeddedBlocks || typeof layout.value.embeddedBlocks !== 'object' || Array.isArray(layout.value.embeddedBlocks)) {
    layout.value.embeddedBlocks = { top: [], bottom: [] }
  }
  embeddedBlockZones.forEach((zone) => {
    if (!Array.isArray(layout.value.embeddedBlocks[zone])) {
      layout.value.embeddedBlocks[zone] = []
    }
  })
}

function ensureLayoutRegions() {
  region('query')
  region('toolbar')
  region('table')
  region('form')
  if (isCardLayout.value) {
    const card = cardRegion()
    const fields = currentColumns.value.map(fieldKey).filter(Boolean)
    if (!Array.isArray(card.fields) || !card.fields.length) {
      card.fields = fields.slice(0, 4)
    }
    if (!card.titleField) {
      card.titleField = fields[0] || ''
    }
    if (!card.imageField) {
      const imageColumn = currentColumns.value.find((column) => ['imageUpload', 'avatar'].includes(column.htmlType))
      card.imageField = imageColumn ? fieldKey(imageColumn) : ''
    }
    const actionsRegion = region('cardActions')
    if (!Array.isArray(actionsRegion.actions)) {
      actionsRegion.actions = ['edit', 'delete']
    }
  }
}

function defaultActions() {
  const toolbarActions = [{ code: 'create', label: '新增', type: 'primary', builtin: true }]
  if (activeMode() === 'admin') {
    toolbarActions.push({ code: 'export', label: '导出', builtin: true })
  }
  return {
    toolbarActions,
    rowActions: [
      { code: 'view', label: '查看', builtin: true },
      { code: 'edit', label: '编辑', builtin: true },
      { code: 'delete', label: '删除', type: 'danger', builtin: true }
    ]
  }
}

function parseJson(value, fallback) {
  if (!value) return JSON.parse(JSON.stringify(fallback))
  try {
    return typeof value === 'string' ? JSON.parse(value) : JSON.parse(JSON.stringify(value))
  } catch (error) {
    return JSON.parse(JSON.stringify(fallback))
  }
}

function formatJson(value) {
  return JSON.stringify(value || {}, null, 2)
}

function markDirty() {
  if (!suppressDirty.value) {
    emit('dirty-change', true)
  }
}

function syncJson(options = {}) {
  layoutJson.value = formatJson(layout.value)
  actionJson.value = formatJson(actions.value)
  if (!options?.silent) {
    markDirty()
  }
}

function actionAllowedForCurrentTable(code) {
  const table = currentTable.value
  if (!table) return true
  if (code === 'create') return table.allowAdd !== '0'
  if (code === 'edit') return table.allowEdit !== '0'
  if (code === 'delete' || code === 'batchDelete') return table.allowRemove !== '0'
  return true
}

function pruneUnavailableActions() {
  region('toolbar').actions = regionActions('toolbar', 'actions').filter(actionAllowedForCurrentTable)
  region('table').rowActions = regionActions('table', 'rowActions').filter(actionAllowedForCurrentTable)
  region('cardActions').actions = regionActions('cardActions', 'actions').filter(actionAllowedForCurrentTable)
}

function region(id) {
  const regions = Array.isArray(layout.value?.regions) ? layout.value.regions : []
  let item = regions.find((entry) => entry.id === id || entry.type === id)
  if (!item) {
    item = defaultRegion(id)
    regions.push(item)
    layout.value.regions = regions
  }
  return item
}

function defaultRegion(id) {
  if (id === 'form') return { id: 'form', type: 'dialogForm', visible: true, fields: [] }
  if (id === 'card') return { id: 'card', type: 'cardList', fields: [], titleField: '', imageField: '' }
  if (id === 'cardActions') return { id: 'cardActions', type: 'actions', actions: ['edit', 'delete'] }
  if (id === 'toolbar') return { id: 'toolbar', type: 'toolbar', visible: true, actions: ['create'] }
  if (id === 'table') return { id: 'table', type: 'table', fields: [], rowActions: ['edit', 'delete'] }
  return { id, type: id, visible: true, fields: [] }
}

function regionFields(id) {
  const item = region(id)
  if (!Array.isArray(item.fields)) item.fields = []
  return item.fields
}

function regionActions(regionId, key) {
  const item = region(regionId)
  if (!Array.isArray(item[key])) item[key] = []
  return item[key]
}

function cardRegion() {
  return region('card')
}

function regionVisible(id) {
  return region(id).visible !== false
}

function setRegionVisible(id, visible) {
  region(id).visible = visible
  syncJson()
}

function fieldKey(column) {
  return column?.javaField || column?.columnName || ''
}

function fieldLabel(field) {
  const column = currentColumns.value.find((item) => fieldKey(item) === field || item.columnName === field)
  return column?.columnComment || column?.javaField || field
}

function hasField(regionId, field) {
  return regionFields(regionId).includes(field)
}

function toggleField(regionId, field) {
  const fields = regionFields(regionId)
  const index = fields.indexOf(field)
  if (index >= 0) {
    fields.splice(index, 1)
  } else {
    fields.push(field)
  }
  syncJson()
}

function removeRegionField(regionId, index) {
  const fields = regionFields(regionId)
  if (!Number.isInteger(index) || index < 0 || index >= fields.length) return
  fields.splice(index, 1)
  syncJson()
}

function startPaletteField(field) {
  dragging.value = { type: 'field', field }
}

function startRegionFieldDrag(regionId, index) {
  dragging.value = { type: 'field', sourceRegionId: regionId, index, field: regionFields(regionId)[index] }
}

function startCardSpecialFieldDrag(key) {
  const field = cardRegion()[key]
  if (field) {
    dragging.value = { type: 'field', field, sourceCardKey: key }
  }
}

function dropCardSpecialField(key) {
  if (!dragging.value || dragging.value.type !== 'field' || !dragging.value.field) return
  cardRegion()[key] = dragging.value.field
  dragging.value = null
  syncJson()
}

function dropPaletteField(regionId, index) {
  if (!dragging.value || dragging.value.type !== 'field' || !dragging.value.field) return
  const targetFields = regionFields(regionId)
  let field = dragging.value.field
  if (dragging.value.sourceRegionId) {
    const sourceFields = regionFields(dragging.value.sourceRegionId)
    const [moved] = sourceFields.splice(dragging.value.index, 1)
    field = moved
  }
  const existingIndex = targetFields.indexOf(field)
  if (existingIndex >= 0) {
    targetFields.splice(existingIndex, 1)
  }
  const insertAt = Number.isInteger(index) ? index : targetFields.length
  targetFields.splice(insertAt, 0, field)
  dragging.value = null
  syncJson()
}

function startPaletteAction(code) {
  dragging.value = { type: 'action', code }
}

function startRegionActionDrag(regionId, index) {
  const key = actionKeyForRegion(regionId)
  dragging.value = { type: 'action', sourceRegionId: regionId, index, code: regionActions(regionId, key)[index] }
}

function dropAction(regionId, index) {
  if (!dragging.value || dragging.value.type !== 'action' || !dragging.value.code) return
  const key = actionKeyForRegion(regionId)
  const targetActions = regionActions(regionId, key)
  let code = dragging.value.code
  if (dragging.value.sourceRegionId) {
    const sourceKey = actionKeyForRegion(dragging.value.sourceRegionId)
    const sourceActions = regionActions(dragging.value.sourceRegionId, sourceKey)
    const [moved] = sourceActions.splice(dragging.value.index, 1)
    code = moved
  }
  const existingIndex = targetActions.indexOf(code)
  if (existingIndex >= 0) {
    targetActions.splice(existingIndex, 1)
  }
  const insertAt = Number.isInteger(index) ? index : targetActions.length
  targetActions.splice(insertAt, 0, code)
  if (isBusinessActionCode(code)) {
    businessButtonConfig(code, true)
    selectBusinessButton(code, regionId)
  } else {
    clearBusinessButtonSelection()
  }
  dragging.value = null
  syncJson()
}

function removeAction(regionId, index) {
  const key = actionKeyForRegion(regionId)
  const values = regionActions(regionId, key)
  if (!Number.isInteger(index) || index < 0 || index >= values.length) return
  const removed = values[index]
  values.splice(index, 1)
  if (rawBusinessActionCode(removed) === selectedBusinessButtonCode.value) {
    clearBusinessButtonSelection()
    selectedConfigType.value = 'page'
  }
  syncJson()
}

function actionKeyForRegion(regionId) {
  return regionId === 'table' ? 'rowActions' : 'actions'
}

function businessActionCode(action) {
  return `business:${action.code}`
}

function isBusinessActionCode(code) {
  return String(code || '').startsWith('business:')
}

function rawBusinessActionCode(code) {
  return String(code || '').replace(/^business:/, '')
}

function businessActionForCode(code) {
  const rawCode = rawBusinessActionCode(code)
  const businessActions = Array.isArray(props.database?.businessActions) ? props.database.businessActions : []
  return businessActions.find((action) => action?.code === rawCode) || null
}

function defaultBusinessButtonConfig(code) {
  const rawCode = rawBusinessActionCode(code)
  const action = businessActionForCode(rawCode)
  const label = action?.name || rawCode
  return {
    label,
    type: 'primary',
    icon: 'el-icon-position',
    confirmMessage: '',
    successMessage: `${label}成功`,
    refreshTarget: 'list'
  }
}

function businessButtonConfig(code, ensure = true) {
  const rawCode = rawBusinessActionCode(code)
  if (!rawCode) return null
  const defaults = defaultBusinessButtonConfig(rawCode)
  const configs = currentBusinessButtonConfigs(ensure)
  const saved = configs?.[rawCode] && typeof configs[rawCode] === 'object' && !Array.isArray(configs[rawCode])
    ? configs[rawCode]
    : {}
  const merged = { ...defaults, ...saved }
  if (ensure && configs) {
    configs[rawCode] = merged
  }
  return merged
}

function currentBusinessButtonConfigs(ensure = false) {
  const configs = layout.value?.businessButtons
  if (configs && typeof configs === 'object' && !Array.isArray(configs)) {
    return configs
  }
  if (!ensure) return {}
  if (!layout.value || typeof layout.value !== 'object') {
    layout.value = defaultLayout()
  }
  layout.value.businessButtons = {}
  return layout.value.businessButtons
}

function setBusinessButtonConfig(key, value) {
  if (!selectedBusinessButtonCode.value) return
  const config = businessButtonConfig(selectedBusinessButtonCode.value, true)
  config[key] = value
  syncJson()
}

function selectActionButton(regionId, index) {
  const key = actionKeyForRegion(regionId)
  const code = regionActions(regionId, key)[index]
  if (isBusinessActionCode(code)) {
    selectBusinessButton(code, regionId)
    syncJson()
    return
  }
  clearBusinessButtonSelection()
}

function selectBusinessButton(code, regionId) {
  selectedBusinessButtonCode.value = rawBusinessActionCode(code)
  selectedBusinessButtonRegion.value = regionId || ''
  businessButtonConfig(code, true)
  selectedBusinessBlock.value = null
  selectedBusinessBlockDefinition.value = null
  selectedBusinessBlockZone.value = ''
  selectedConfigType.value = 'businessButton'
}

function clearBusinessButtonSelection() {
  selectedBusinessButtonCode.value = ''
  selectedBusinessButtonRegion.value = ''
}

function businessActionMatchesCurrentTable(action) {
  const currentTableName = draft.value?.tableName || ''
  const ownerTable = action?.ownerTable || ''
  return !currentTableName || !ownerTable || ownerTable === currentTableName
}

function sortBusinessActionPalette(left, right) {
  const tableRank = Number(right.matchesCurrentTable) - Number(left.matchesCurrentTable)
  if (tableRank !== 0) return tableRank
  return left.label.localeCompare(right.label, 'zh-CN')
}

function actionLabel(code) {
  if (isBusinessActionCode(code)) {
    const config = businessButtonConfig(code, false)
    if (config?.label) return config.label
  }
  const action = actionPalette.value.find((item) => item.code === code)
  if (action) return action.label
  return String(code || '').replace(/^business:/, '')
}

function toggleAction(kind, code) {
  const regionId = kind === 'toolbar' ? 'toolbar' : 'table'
  const key = kind === 'toolbar' ? 'actions' : 'rowActions'
  const values = regionActions(regionId, key)
  const index = values.indexOf(code)
  if (index >= 0) {
    values.splice(index, 1)
  } else {
    values.push(code)
    if (isBusinessActionCode(code)) {
      businessButtonConfig(code, true)
      selectBusinessButton(code, regionId)
    }
  }
  syncJson()
}

function setCardField(key, value) {
  cardRegion()[key] = value || ''
  syncJson()
}

function resetLayoutForTable() {
  const fields = currentColumns.value.map(fieldKey).filter(Boolean)
  region('query').fields = currentColumns.value.filter((column) => column.isQuery === '1').map(fieldKey).filter(Boolean)
  region('table').fields = fields.filter((field) => field)
  region('form').fields = fields.filter((field) => field)
  const card = cardRegion()
  card.fields = fields.slice(0, 4)
  card.titleField = fields[0] || ''
  const imageColumn = currentColumns.value.find((column) => ['imageUpload', 'avatar'].includes(column.htmlType))
  card.imageField = imageColumn ? fieldKey(imageColumn) : ''
  pruneUnavailableActions()
  syncJson()
}

function applyLayoutJson() {
  try {
    layout.value = JSON.parse(layoutJson.value || '{}')
    syncLayoutForPageType()
    ElMessage.success('布局 JSON 已应用')
  } catch (error) {
    ElMessage.warning('布局 JSON 格式不正确')
    syncJson()
  }
}

function applyBusinessBlockLayoutJson(value) {
  try {
    layoutJson.value = value
    layout.value = JSON.parse(value || '{}')
    syncLayoutForPageType()
  } catch (error) {
    layout.value = { canvas: 'business-blocks-v1', blocks: [] }
    syncJson()
  }
}

function handleBusinessBlockSelect(payload) {
  selectedBusinessBlock.value = payload?.block || null
  selectedBusinessBlockDefinition.value = payload?.definition || null
  selectedBusinessBlockZone.value = ''
  clearBusinessButtonSelection()
  if (payload?.block) {
    selectedConfigType.value = 'businessBlock'
  }
}

function updateSelectedBusinessBlockConfig(config) {
  updateSelectedBusinessBlockPatch({ config: { ...(config || {}) } })
}

function updateSelectedBusinessBlockDataset(dataset) {
  updateSelectedBusinessBlockPatch({ dataset })
}

function updateSelectedBusinessBlockDisplay(display) {
  updateSelectedBusinessBlockPatch({ display })
}

function updateSelectedBusinessBlockSpan(span) {
  updateSelectedBusinessBlockPatch({ span })
}

function updateSelectedBusinessBlockPatch(patch) {
  if (!selectedBusinessBlock.value) return
  if (selectedBusinessBlockZone.value) {
    const blocks = embeddedBusinessBlocks(selectedBusinessBlockZone.value)
    const target = blocks.find((block) => block.id === selectedBusinessBlock.value.id)
    if (target) {
      Object.assign(target, patch)
      selectedBusinessBlock.value = target
      syncJson()
    }
    return
  }
  const nextLayout = {
    ...layout.value,
    blocks: (layout.value.blocks || []).map((block) => (
      block.id === selectedBusinessBlock.value.id ? { ...block, ...patch } : block
    ))
  }
  applyBusinessBlockLayoutJson(formatJson(nextLayout))
}

function applyActionJson() {
  try {
    actions.value = JSON.parse(actionJson.value || '{}')
    syncJson()
    ElMessage.success('按钮 JSON 已应用')
  } catch (error) {
    ElMessage.warning('按钮 JSON 格式不正确')
    syncJson()
  }
}

function saveCurrent() {
  const payload = commitCurrentDraft()
  if (payload) {
    emit('save-design', payload)
  }
}

function commitCurrentDraft() {
  const payload = buildCurrentPayload()
  if (!payload) return null
  const exists = props.designs.some((item) => designKey(item) === selectedKey.value)
  const nextDesigns = exists
    ? props.designs.map((item) => designKey(item) === selectedKey.value ? payload : item)
    : props.designs.concat(payload)
  emit('update:designs', nextDesigns)
  return payload
}

function buildCurrentPayload() {
  if (!draft.value) return null
  return {
    ...draft.value,
    pageScope: draft.value.pageScope || activeMode(),
    layoutJson: formatJson(layout.value),
    actionJson: formatJson(actions.value)
  }
}

defineExpose({ commitCurrentDraft })
</script>

<style scoped lang="scss">
.page-designer {
  display: block;
}

.designer-head,
.band-title,
.matrix-head,
.head-actions,
.band-tools {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.designer-desc {
  margin: 4px 0 0;
  font-size: 14px;
  line-height: 1.45;
}

.head-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
}

.page-designer-body {
  padding: 20px;
}

.designer-empty {
  min-height: 280px;
  padding: 24px 0;
}

:deep(.designer-empty .el-empty__description) {
  margin-top: 4px;
}

:deep(.designer-empty .el-empty__bottom) {
  margin-top: 18px;
}

.designer-shell {
  display: grid;
  gap: 16px;
}

.page-menu {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  overflow-x: auto;
  padding: 4px 2px 8px;
}

.page-menu-item {
  position: relative;
  flex: 0 0 auto;
  min-width: 144px;
  max-width: 220px;
}

.page-menu-select,
.page-menu-add {
  display: grid;
  gap: 4px;
  width: 100%;
  min-height: 58px;
  padding: 10px 12px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fbfdff;
  color: #303133;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

.page-menu-item.active .page-menu-select {
  border-color: #409eff;
  background: #ecf5ff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.08);
}

.page-menu-select span {
  overflow: hidden;
  color: #909399;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-menu-delete {
  position: absolute;
  top: 6px;
  right: 6px;
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #98a2b3;
  opacity: 0;
  cursor: pointer;
}

.page-menu-item:hover .page-menu-delete,
.page-menu-delete:focus-visible {
  opacity: 1;
}

.page-menu-delete:hover {
  background: #fee2e2;
  color: #d92d20;
}

.page-menu-add {
  place-items: center;
  min-width: 110px;
  color: #2563eb;
  font-weight: 600;
}

.designer-grid {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr) 320px;
  gap: 16px;
}

.material-pane,
.canvas,
.property-pane {
  min-width: 0;
}

.material-pane {
  position: sticky;
  top: 92px;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: calc(100vh - 112px);
  min-height: 360px;
  overflow: hidden;
}

.material-pane-tabs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(44px, 1fr));
  gap: 4px;
  padding: 4px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #f4f7fb;
}

.material-pane-tabs button {
  min-width: 0;
  height: 32px;
  padding: 0 4px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: #667085;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}

.material-pane-tabs button.active {
  background: #409eff;
  color: #fff;
  font-weight: 700;
  box-shadow: 0 1px 3px rgb(64 158 255 / 22%);
}

.material-pane-tabs button:focus-visible {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

.material-panel-scroll {
  flex: 1 1 auto;
  display: grid;
  align-content: start;
  gap: 12px;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 2px;
}

.material-panel {
  display: grid;
  gap: 12px;
}

.quick-actions,
.associated-page-list,
.menu-config-list {
  display: grid;
  gap: 8px;
}

.associated-page-list button,
.quick-actions button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-height: 36px;
  padding: 8px 10px;
  border: 1px solid #e7ecf3;
  border-radius: 6px;
  background: #fff;
  color: #303133;
  text-align: left;
  cursor: pointer;
}

.associated-page-list small {
  color: #909399;
  white-space: nowrap;
}

.associated-page-list button {
  display: grid;
  justify-content: stretch;
  gap: 3px;
}

.associated-page-list button span,
.associated-page-list button small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.associated-page-list button.active {
  border-color: #409eff;
  background: #ecf5ff;
  color: #1d4ed8;
}

.quick-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.quick-actions button {
  justify-content: center;
  color: #2563eb;
  font-weight: 700;
}

.optimized-page-actions {
  grid-template-columns: 1fr;
  gap: 10px;
}

.optimized-page-actions button {
  justify-content: flex-start;
  min-width: 0;
  border-color: #d8e4f8;
  color: #1f2a44;
  font-weight: 600;
  transition: border-color 0.16s ease, background 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}

.optimized-page-actions button:hover {
  border-color: #9cc6ff;
  background: #f7fbff;
  box-shadow: 0 6px 14px rgb(31 42 68 / 7%);
  transform: translateY(-1px);
}

.optimized-page-actions button:focus-visible {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

.primary-page-action {
  min-height: 62px;
  padding: 12px;
  border-color: #acd1ff;
  background: linear-gradient(180deg, #f3f8ff 0%, #ffffff 100%);
}

.secondary-page-actions {
  display: grid;
  gap: 8px;
}

.secondary-page-actions button {
  min-height: 44px;
  padding: 8px 10px;
}

.quick-action-icon {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  background: #ecf5ff;
  color: #1d70ff;
}

.primary-page-action .quick-action-icon {
  width: 34px;
  height: 34px;
  background: #409eff;
  color: #fff;
}

.quick-action-meta {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.quick-action-meta strong,
.quick-action-meta small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.quick-action-meta strong {
  color: #172033;
  font-size: 13px;
  line-height: 1.2;
}

.quick-action-meta small {
  color: #667085;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.2;
}

.band-title small,
.matrix-head small {
  color: #909399;
  font-size: 12px;
}

.canvas {
  display: grid;
  align-content: start;
  gap: 12px;
  overflow: visible;
}

.canvas.menu-position-side {
  grid-template-columns: 220px minmax(0, 1fr);
  align-items: start;
}

.canvas.menu-position-side .canvas-page-menu {
  position: sticky;
  top: 92px;
  grid-row: 1 / span 99;
  grid-column: 1;
}

.canvas.menu-position-side > :not(.canvas-page-menu) {
  grid-column: 2;
}

.canvas-page-menu {
  position: relative;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 64px;
  padding: 10px 14px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
  cursor: pointer;
}

.canvas-page-menu.menu-position-side {
  align-items: stretch;
  flex-direction: column;
  justify-content: flex-start;
}

.canvas-page-menu.active {
  border-color: #409eff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.12);
}

.canvas-menu-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 126px;
  white-space: nowrap;
}

.canvas-menu-brand span {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: #14532d;
  color: #fff;
  font-size: 12px;
  font-weight: 800;
}

.canvas-menu-brand strong {
  color: #172033;
  font-size: 15px;
}

.canvas-page-menu-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.canvas-page-menu.menu-position-side .canvas-menu-brand {
  min-width: 0;
}

.canvas-page-menu.menu-position-side .canvas-page-menu-bar {
  align-items: stretch;
  flex: 0 0 auto;
  flex-direction: column;
  justify-content: flex-start;
  width: 100%;
}

.canvas-page-menu.menu-position-side .canvas-menu-group {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}

.canvas-page-menu.menu-position-side .canvas-menu-parent {
  flex: 1 1 auto;
  justify-content: flex-start;
  text-align: left;
}

.canvas-page-menu.menu-position-side .canvas-menu-dropdown {
  position: static;
  flex: 0 0 100%;
  min-width: 0;
  margin-top: 4px;
  box-shadow: none;
}

.canvas-page-menu.menu-display-expanded .canvas-menu-dropdown {
  display: grid;
  gap: 4px;
}

.canvas-menu-group {
  display: inline-flex;
  align-items: center;
  position: relative;
  border-radius: 6px;
}

.canvas-menu-parent,
.canvas-menu-toggle {
  min-height: 38px;
  border: 0;
  background: transparent;
  color: #344054;
  font-weight: 700;
  white-space: nowrap;
}

.canvas-menu-parent {
  padding: 0 12px;
  border-radius: 6px;
}

.canvas-menu-group.has-children .canvas-menu-parent {
  border-radius: 6px 0 0 6px;
}

.canvas-menu-toggle {
  display: inline-grid;
  width: 30px;
  place-items: center;
  border-radius: 0 6px 6px 0;
}

.canvas-menu-group.active .canvas-menu-parent,
.canvas-menu-parent:hover,
.canvas-menu-toggle:hover {
  background: #eef6ff;
  color: #2563eb;
}

.canvas-menu-group.directory .canvas-menu-parent {
  color: #14532d;
}

.canvas-menu-group.dragging {
  opacity: 0.55;
}

.canvas-menu-dropdown {
  position: absolute;
  top: 44px;
  left: 0;
  display: none;
  min-width: 190px;
  padding: 8px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fff;
  box-shadow: 0 16px 36px rgba(15, 23, 42, 0.14);
}

.canvas-menu-group.open .canvas-menu-dropdown {
  display: grid;
  gap: 4px;
}

.canvas-menu-child {
  display: grid;
  gap: 2px;
  width: 100%;
  min-height: 38px;
  padding: 6px 10px;
  border: 0;
  border-radius: 6px;
  background: #fff;
  color: #344054;
  text-align: left;
}

.canvas-menu-child.active,
.canvas-menu-child:hover {
  background: #ecf5ff;
  color: #1d4ed8;
}

.canvas-menu-child span,
.canvas-menu-child small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.canvas-menu-child small {
  color: #909399;
}

.canvas-menu-empty {
  color: #98a2b3;
  font-size: 12px;
  white-space: nowrap;
}

.layout-preview-head,
.restore-bar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fff;
}

.restore-bar {
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
}

.restore-bar > span {
  color: #667085;
  font-size: 12px;
}

.layout-preview-head > div {
  display: grid;
  gap: 4px;
}

.layout-preview-head strong {
  color: #172033;
  font-size: 14px;
}

.layout-preview-head span,
.layout-help {
  margin: 0;
  color: #667085;
  font-size: 12px;
  line-height: 1.5;
}

.menu-level-switch {
  display: inline-flex;
  padding: 4px;
  border-radius: 6px;
  background: #f4f7fb;
}

.menu-level-switch button {
  min-width: 84px;
  height: 32px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: #4b5565;
  font-size: 14px;
  cursor: pointer;
}

.menu-level-switch button.active {
  background: #409eff;
  color: #fff;
  box-shadow: 0 1px 3px rgb(64 158 255 / 24%);
}

.menu-level-switch button:focus-visible {
  outline: 2px solid #93c5fd;
  outline-offset: 2px;
}

.route-target-select {
  width: 100%;
  margin-top: 8px;
}

.canvas-band,
.property-pane,
.material-section,
.add-content-materials,
.associated-page-materials,
.canvas-menu-config,
.palette-panel {
  padding: 16px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fbfdff;
}

.chip-row,
.table-preview,
.action-row,
.table-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.drop-zone.empty {
  min-height: 42px;
  align-items: center;
  justify-content: center;
  border: 1px dashed #cbd5e1;
  border-radius: 8px;
  color: #94a3b8;
}

.drop-hint,
.drop-zone em {
  color: #94a3b8;
  font-size: 12px;
  font-style: normal;
}

.canvas-palette-shortcut {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 28px;
  padding: 5px 10px;
  border: 1px solid #bfdbfe;
  border-radius: 4px;
  background: #eff6ff;
  color: #2563eb;
  font-weight: 600;
  cursor: pointer;
}

.canvas-palette-shortcut:hover {
  border-color: #93c5fd;
  background: #dbeafe;
}

.field-chip,
.table-preview > span,
.action-chip,
.card-field-chip,
.card-actions button,
.table-actions button,
.material-section :deep(.palette-item),
.palette-list button {
  min-height: 32px;
  padding: 6px 10px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: #fff;
  color: #303133;
  cursor: grab;
}

.field-chip,
.table-preview > span,
.card-field-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.action-chip,
.card-actions button,
.table-actions button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.table-actions {
  width: 100%;
  padding: 8px;
  border: 1px dashed #d8e1ec;
  border-radius: 8px;
  background: #f8fbff;
}

.table-actions button {
  min-width: 88px;
}

.chip-remove {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  color: #98a2b3;
  cursor: pointer;
}

.chip-remove:hover {
  background: #fee2e2;
  color: #d92d20;
}

.chip-remove .el-icon {
  font-size: 12px;
}

.table-band {
  min-height: 180px;
}

.layout-preview-card {
  background: #f9fbff;
}

.card-canvas {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-top: 12px;
}

.card-preview-item {
  display: grid;
  gap: 8px;
  min-height: 210px;
  padding: 12px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fff;
}

.card-image-slot {
  display: grid;
  place-items: center;
  min-height: 72px;
  border-radius: 6px;
  background: #e8f3ff;
  color: #2563eb;
  font-size: 12px;
}

.card-image-slot.empty {
  border: 1px dashed #cbd5e1;
  background: #f8fafc;
  color: #94a3b8;
}

.card-title-slot {
  color: #172033;
  font-size: 14px;
  cursor: grab;
}

.card-field-list,
.card-actions {
  display: grid;
  gap: 6px;
}

.card-field-chip {
  color: #667085;
  font-size: 12px;
  cursor: grab;
}

.card-actions {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.split-preview {
  display: grid;
  grid-template-columns: 130px minmax(0, 1fr);
  gap: 10px;
  margin-top: 12px;
}

.split-sidebar {
  display: grid;
  align-content: start;
  gap: 8px;
  padding: 10px;
  border: 1px solid #e7ecf3;
  border-radius: 8px;
  background: #fff;
}

.split-sidebar span {
  overflow: hidden;
  color: #667085;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.form-preview {
  display: grid;
  gap: 10px;
  margin-top: 12px;
}

.form-preview label {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  cursor: grab;
}

.property-pane {
  display: grid;
  align-content: start;
  gap: 14px;
}

.field-matrix,
.action-editor,
.material-section,
.add-content-materials,
.associated-page-materials,
.canvas-menu-config,
.palette-panel {
  display: grid;
  gap: 10px;
}

.business-block-config-panel {
  background: #fff;
}

.canvas-menu-config {
  background: #fff;
}

.menu-config-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 36px;
  padding: 8px 10px;
  border: 1px solid #e7ecf3;
  border-radius: 6px;
  background: #fbfdff;
}

.menu-config-row span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-config-row small {
  color: #909399;
  white-space: nowrap;
}

.embedded-block-band {
  background: #fff;
}

.embedded-block-list {
  display: grid;
  gap: 8px;
  margin-top: 12px;
}

.embedded-block-grid {
  grid-template-columns: repeat(12, minmax(0, 1fr));
}

.embedded-block-chip {
  position: relative;
  display: block;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 34px;
  padding: 7px 10px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  background: #fff;
  color: #303133;
  cursor: grab;
}

.embedded-block-chip .chip-remove {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
}

.embedded-block-chip.active {
  border-color: #409eff;
  background: #ecf5ff;
  color: #1d4ed8;
}

@media (max-width: 900px) {
  .embedded-block-chip {
    grid-column: 1 / -1 !important;
  }
}

.palette-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.material-empty {
  color: #98a2b3;
  font-size: 12px;
  line-height: 32px;
}

.business-materials :deep(.block-palette) {
  gap: 10px;
}

.business-materials :deep(.palette-title) {
  display: none;
}

.action-palette button {
  color: #2563eb;
}

.business-action-palette button {
  display: grid;
  justify-items: start;
  gap: 2px;
  width: 100%;
  color: #175cd3;
  text-align: left;
}

.business-action-palette button small {
  color: #667085;
  font-size: 12px;
}

.business-action-palette button.unmatched {
  border-color: #e4e7ec;
  color: #667085;
}

.field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 42px 42px 42px;
  align-items: center;
  gap: 6px;
}

.field-row > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.json-input {
  margin-top: 10px;
}

@media (max-width: 1180px) {
  .designer-grid {
    grid-template-columns: 200px minmax(0, 1fr);
  }

  .property-pane {
    grid-column: 1 / -1;
  }
}

@media (max-width: 760px) {
  .designer-grid {
    grid-template-columns: 1fr;
  }

  .material-pane {
    position: sticky;
    top: 8px;
    z-index: 6;
    height: min(58vh, 520px);
    min-height: 320px;
    overflow: hidden;
  }

  .material-panel-scroll {
    overflow-y: auto;
  }

  .canvas-page-menu {
    align-items: stretch;
    flex-direction: column;
  }

  .canvas.menu-position-side {
    grid-template-columns: 1fr;
  }

  .canvas.menu-position-side .canvas-page-menu,
  .canvas.menu-position-side > :not(.canvas-page-menu) {
    grid-column: 1;
  }

  .canvas.menu-position-side .canvas-page-menu {
    position: relative;
    top: auto;
    grid-row: auto;
  }

  .canvas-page-menu-bar {
    justify-content: flex-start;
    overflow-x: auto;
  }

  .card-canvas,
  .split-preview {
    grid-template-columns: 1fr;
  }

  .designer-head {
    align-items: stretch;
    flex-direction: column;
  }

  .head-actions {
    justify-content: flex-start;
  }
}
</style>
