<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">
            <div class="add-content-materials">
              <div class="matrix-head">
                <strong>添加内容</strong>
                <small>页面、字段、按钮和业务块</small>
              </div>
              <div class="quick-actions">
                <button type="button" @click="addPageDesign('primary')">新增一级菜单</button>
                <button type="button" @click="addPageDesign('secondary')">新增二级页面</button>
                <button type="button" @click="selectMenuConfig">配置菜单</button>
              </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 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-if="!isBusinessBlockLayout" class="material-section action-materials">
              <div class="matrix-head">
                <strong>按钮池</strong>
                <small>拖入按钮区域</small>
              </div>
              <div class="palette-list action-palette">
                <button
                  v-for="action in actionPalette"
                  :key="`action-palette-${action.code}`"
                  type="button"
                  draggable="true"
                  @dragstart="startPaletteAction(action.code)"
                >
                  {{ action.label }}
                </button>
              </div>
            </div>

            <div 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-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>
          </aside>

        <main class="canvas">
          <header
            class="canvas-page-menu"
            :class="{ active: selectedConfigType === 'menu' }"
            @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), 'has-children': group.children.length }"
              >
                <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 drop-zone" :class="{ empty: !embeddedBusinessBlocks('top').length }">
              <button
                v-for="(block, index) in embeddedBusinessBlocks('top')"
                :key="`top-embedded-${block.id || index}`"
                type="button"
                class="embedded-block-chip"
                :class="{ active: selectedBusinessBlock?.id === block.id }"
                draggable="true"
                @click="selectEmbeddedBusinessBlock(block, 'top')"
                @dragstart="startEmbeddedBlockInstanceDrag('top', index)"
                @dragover.prevent
                @drop.stop="dropEmbeddedBusinessBlock('top', index)"
              >
                <span>{{ 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>
              </button>
              <span v-if="!embeddedBusinessBlocks('top').length" class="drop-hint">拖入公告、轮播等可嵌入业务块</span>
            </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>
              <span v-if="!regionFields('query').length" class="drop-hint">把字段拖到这里生成查询条件</span>
            </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)"
              >
                <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>
              <span v-if="!toolbarActions.length" class="drop-hint">把按钮拖到这里生成顶部操作</span>
            </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>
                  <em v-if="!cardFields.length">拖入卡片展示字段</em>
                </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)"
                  >
                    <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>
                  <em v-if="!cardActions.length">拖入卡片按钮</em>
                </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>
                <em v-if="!regionFields('query').length">拖入左侧筛选字段</em>
              </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)"
                  >
                    <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>
                  <em v-if="!rowActions.length">拖入行按钮</em>
                </div>
                <em v-if="!regionFields('table').length">拖入列表字段</em>
              </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)"
                >
                  <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>
                <em v-if="!rowActions.length">拖入行按钮</em>
              </div>
              <em v-if="!regionFields('table').length">拖入表格字段</em>
            </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>
              <span v-if="!regionFields('form').length" class="drop-hint">把字段拖到这里生成表单项</span>
            </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 drop-zone" :class="{ empty: !embeddedBusinessBlocks('bottom').length }">
              <button
                v-for="(block, index) in embeddedBusinessBlocks('bottom')"
                :key="`bottom-embedded-${block.id || index}`"
                type="button"
                class="embedded-block-chip"
                :class="{ active: selectedBusinessBlock?.id === block.id }"
                draggable="true"
                @click="selectEmbeddedBusinessBlock(block, 'bottom')"
                @dragstart="startEmbeddedBlockInstanceDrag('bottom', index)"
                @dragover.prevent
                @drop.stop="dropEmbeddedBusinessBlock('bottom', index)"
              >
                <span>{{ 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>
              </button>
              <span v-if="!embeddedBusinessBlocks('bottom').length" class="drop-hint">拖入公告、轮播等可嵌入业务块</span>
            </div>
          </section>
          </template>
        </main>

        <aside class="property-pane">
          <div v-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>

          <el-form v-else-if="draft" label-position="top">
            <el-form-item label="页面名称">
              <el-input v-model="draft.pageName" @input="markDirty" />
            </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
                v-model="navigationConfig.showInMenu"
                active-text="显示"
                inactive-text="隐藏"
                @change="syncJson"
              />
              <p class="layout-help">关闭后页面仍会生成路由，可通过列表、卡片、按钮或业务块跳转进入。</p>
            </el-form-item>
            <el-form-item v-if="navigationConfig.showInMenu" label="菜单层级">
              <el-segmented :model-value="currentMenuLevel" :options="menuLevelOptions" @change="setMenuLevel" />
            </el-form-item>
            <el-form-item v-if="navigationConfig.showInMenu && currentMenuLevel === 'primary'" label="菜单显示名称">
              <el-input v-model="navigationConfig.menuName" placeholder="为空时使用页面名称" @input="syncJson" />
            </el-form-item>
            <el-form-item v-if="navigationConfig.showInMenu && currentMenuLevel === 'secondary'" label="所属一级菜单">
              <el-select :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="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>
            <BlockConfigForm
              :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 } 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 { updateBlockConfig } from '@/components/business-blocks/blockLayout.js'
import {
  buildPageMenuGroups,
  isAssociatedPageType,
  pageGroupKey as resolvePageGroupKey,
  pageGroupLabel as resolvePageGroupLabel,
  pageMenuLevel,
  readNavigationConfig,
  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 pendingSelectPageCode = ref('')
const selectedConfigType = ref('page')
const openMenuGroupKey = 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: '拖入购物车、公告、轮播等完整业务块，只配置块需要的数据表和字段。' }
  ]
}

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: '把字段按业务区域分组，适合字段较多的后台录入页。' }
  ]
}

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

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,
      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 embeddedBlockZones = ['top', 'bottom']

const tables = computed(() => Array.isArray(props.database?.tables) ? props.database.tables : [])
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 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 navigationConfig = computed(() => ensureNavigationConfig())
const pageMenuDesigns = computed(() => {
  if (!draft.value || !selectedKey.value) return props.designs
  const current = {
    ...draft.value,
    layoutJson: formatJson(layout.value)
  }
  const hasSelected = props.designs.some((design) => designKey(design) === selectedKey.value)
  const designs = hasSelected
    ? props.designs.map((design) => designKey(design) === selectedKey.value ? current : design)
    : props.designs.concat(current)
  return designs
})
const editableMenuPages = computed(() => pageMenuDesigns.value.filter(shouldShowInMenu))
const associatedPages = computed(() => pageMenuDesigns.value.filter((design) => !shouldShowInMenu(design)))
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 visibleAssociatedPages = computed(() => {
  if (selectedConfigType.value !== 'page') return []
  if (selectedMenuGroup.value?.children.length) return []
  return associatedPages.value
})
const parentMenuOptions = computed(() => {
  const groups = pageMenuGroups.value.filter((group) => designKey(group.primary) !== selectedKey.value)
  return groups.length ? groups : pageMenuGroups.value
})
const currentMenuLevel = computed(() => {
  if (!draft.value) return 'primary'
  const explicitLevel = pageMenuLevel(draft.value)
  if (explicitLevel) return explicitLevel
  return pageMenuGroups.value.some((group) => designKey(group.primary) === selectedKey.value) ? 'primary' : 'secondary'
})
const currentParentMenuCode = computed(() => {
  if (!draft.value) return ''
  return ensureNavigationConfig().parentMenuCode || pageGroupKey(draft.value)
})

const menuLevelOptions = [
  { label: '一级菜单', value: 'primary' },
  { label: '二级页面', value: 'secondary' }
]

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

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

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

function pageGroupKey(design) {
  return resolvePageGroupKey(design, activeMode())
}

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'
  selectDesign(design)
}

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

function selectMenuConfig() {
  selectedConfigType.value = 'menu'
  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 || {}))
    selectedConfigType.value = 'page'
    handleBusinessBlockSelect(null)
    layout.value = parseJson(draft.value.layoutJson, defaultLayout(draft.value?.pageType || 'list'))
    actions.value = parseJson(draft.value.actionJson, defaultActions())
    syncLayoutForPageType()
    syncJson({ silent: true })
  } finally {
    suppressDirty.value = false
  }
}

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

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

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

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

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

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 instance = {
    id: nextBusinessBlockId(definition.code),
    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 : ''
  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'
}

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: 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 setNavigationConfig(key, value) {
  const config = ensureNavigationConfig()
  config[key] = value
  syncJson()
}

function setMenuLevel(value) {
  const level = value === 'secondary' ? 'secondary' : 'primary'
  const config = ensureNavigationConfig()
  config.menuLevel = level
  if (level === 'primary') {
    config.menuCode = draft.value?.pageCode || config.menuCode
    config.menuName = draft.value?.pageName || config.menuName
    delete config.parentMenuCode
    delete config.parentMenuName
    if (draft.value?.pageCode) {
      draft.value.menuCode = draft.value.pageCode
    }
  } else {
    const parent = defaultParentMenuGroup()
    if (parent) {
      config.parentMenuCode = parent.key
      config.parentMenuName = parent.label
      if (draft.value) {
        draft.value.menuCode = parent.key
      }
    }
    delete config.menuName
  }
  syncJson()
}

function setParentMenuGroup(key) {
  const parent = pageMenuGroups.value.find((group) => group.key === key)
  if (!parent) return
  const config = ensureNavigationConfig()
  config.menuLevel = 'secondary'
  config.parentMenuCode = parent.key
  config.parentMenuName = parent.label
  delete config.menuName
  if (draft.value) {
    draft.value.menuCode = parent.key
  }
  syncJson()
}

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 (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)
  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
  values.splice(index, 1)
  syncJson()
}

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

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

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) {
  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)
  }
  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 = ''
  if (payload?.block) {
    selectedConfigType.value = 'businessBlock'
  }
}

function updateSelectedBusinessBlockConfig(config) {
  if (!selectedBusinessBlock.value) return
  if (selectedBusinessBlockZone.value) {
    const blocks = embeddedBusinessBlocks(selectedBusinessBlockZone.value)
    const target = blocks.find((block) => block.id === selectedBusinessBlock.value.id)
    if (target) {
      target.config = { ...(config || {}) }
      selectedBusinessBlock.value = target
      syncJson()
    }
    return
  }
  const nextLayout = updateBlockConfig(layout.value, selectedBusinessBlock.value.id, config)
  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 {
  display: grid;
  align-content: start;
  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;
}

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

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

.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.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-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-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;
}

.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;
}

.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: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.embedded-block-chip {
  display: inline-flex;
  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.active {
  border-color: #409eff;
  background: #ecf5ff;
  color: #1d4ed8;
}

.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;
}

.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;
  }

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

  .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>
