<template>
  <a-drawer
    :open="open"
    :title="null"
    :width="720"
    :body-style="{ padding: 0 }"
    :header-style="{ display: 'none' }"
    destroy-on-close
    @close="$emit('update:open', false)"
  >
    <!-- 头部 -->
    <div class="detail-header">
      <div class="header-icon-wrap">
        <img v-if="app?.icon" :src="app.icon" class="header-icon-img" />
        <div v-else class="header-icon-placeholder" :style="{ background: iconBgColor(app?.productName) }">
          {{ (app?.productName || 'A').charAt(0).toUpperCase() }}
        </div>
      </div>
      <div class="header-info">
        <div class="header-title">{{ app?.productName || '-' }}</div>
        <div class="header-sub">
          <span v-if="app?.productCode" class="header-code">{{ app.productCode }}</span>
          <span class="header-status" :class="`status-${app?.status}`">{{ statusText(app?.status) }}</span>
        </div>
      </div>
      <div class="header-actions">
        <template v-if="app" v-for="entry in getAppEntries(app)" :key="entry.type">
          <a-button
            v-if="entry.available"
            :type="entry.isPrimary ? 'primary' : 'default'"
            size="small"
            @click="handleEntryClick(entry)"
          >
            <template #icon><component :is="entry.icon" /></template>
            {{ entry.label }}
          </a-button>
        </template>
      </div>
      <a-button type="text" class="close-btn" @click="$emit('update:open', false)">
        <template #icon><CloseOutlined /></template>
      </a-button>
    </div>

    <!-- Tab 导航 -->
    <a-tabs v-model:active-key="activeTab" class="detail-tabs">
      <!-- ====== 成员 Tab ====== -->
      <a-tab-pane key="members" tab="成员">
        <div class="tab-content">
          <div class="tab-toolbar">
            <span class="tab-count">共 {{ members.length }} 位成员</span>
            <a-button type="primary" size="small" @click="showInvite = true">
              <template #icon><PlusOutlined /></template>
              邀请成员
            </a-button>
          </div>

          <a-spin :spinning="membersLoading">
            <div v-if="members.length === 0 && !membersLoading" class="empty-wrap">
              <a-empty description="暂无成员" />
            </div>
            <div v-else class="member-list">
              <div v-for="m in members" :key="m.id" class="member-item" :class="{ 'pending': m.inviteStatus === 1 }">
                <a-avatar :src="m.userAvatar || m.avatar" size="36" class="member-avatar">
                  {{ (m.nickname || m.username || '?').charAt(0).toUpperCase() }}
                </a-avatar>
                <div class="member-info">
                  <div class="member-name">
                    {{ m.nickname || m.username || '-' }}
                    <a-tag v-if="m.inviteStatus === 1" color="warning" size="small">待确认</a-tag>
                    <a-tag v-else-if="m.inviteStatus === 2" color="red" size="small">已拒绝</a-tag>
                  </div>
                  <div class="member-sub">
                    {{ m.phone || '' }}
                    <span v-if="m.inviteStatus === 1 && m.inviteExpireTime" class="expire-hint">
                      (有效期至: {{ m.inviteExpireTime }})
                    </span>
                  </div>
                </div>
                <div class="member-right">
                  <a-select
                    :value="m.role"
                    size="small"
                    style="width: 110px"
                    :disabled="m.role === 'owner' || m.inviteStatus !== 0"
                    @change="(val: string) => handleRoleChange(m, val)"
                  >
                    <a-select-option value="admin">管理员</a-select-option>
                    <a-select-option value="developer">开发者</a-select-option>
                    <a-select-option value="viewer">只读</a-select-option>
                  </a-select>
                  <a-popconfirm
                    v-if="m.role !== 'owner'"
                    :title="m.inviteStatus === 1 ? '确认取消该邀请？' : '确认移除该成员？'"
                    @confirm="handleRemoveMember(m)"
                  >
                    <a-button type="text" danger size="small">
                      {{ m.inviteStatus === 1 ? '取消邀请' : '移除' }}
                    </a-button>
                  </a-popconfirm>
                </div>
              </div>
            </div>
          </a-spin>
        </div>
      </a-tab-pane>

      <!-- ====== 密钥 Tab ====== -->
      <a-tab-pane key="credentials" tab="密钥凭证">
        <div class="tab-content">
          <div class="tab-toolbar">
            <span class="tab-count">共 {{ credentials.length }} 个凭证</span>
            <a-button type="primary" size="small" @click="showAddCredential = true">
              <template #icon><PlusOutlined /></template>
              新增凭证
            </a-button>
          </div>

          <a-spin :spinning="credentialsLoading">
            <div v-if="credentials.length === 0 && !credentialsLoading" class="empty-wrap">
              <a-empty description="暂无凭证" />
            </div>
            <div v-else class="credential-list">
              <div v-for="c in credentials" :key="c.id" class="credential-item">
                <div class="credential-header">
                  <span class="credential-name">{{ c.name || '未命名凭证' }}</span>
                  <a-tag :color="c.type === 'server' ? 'blue' : c.type === 'webhook' ? 'purple' : 'green'">
                    {{ c.type || 'server' }}
                  </a-tag>
                  <a-tag :color="c.status === 0 ? 'success' : 'error'">
                    {{ c.status === 0 ? '正常' : '已冻结' }}
                  </a-tag>
                  <div class="credential-actions">
                    <a-tooltip title="重置密钥">
                      <a-popconfirm title="重置后旧密钥立即失效，确认？" @confirm="handleResetSecret(c)">
                        <a-button type="text" size="small"><template #icon><ReloadOutlined /></template></a-button>
                      </a-popconfirm>
                    </a-tooltip>
                    <a-tooltip :title="c.status === 0 ? '冻结' : '启用'">
                      <a-button type="text" size="small" @click="handleToggleCredentialStatus(c)">
                        <template #icon>
                          <StopOutlined v-if="c.status === 0" style="color: #ff4d4f" />
                          <CheckCircleOutlined v-else style="color: #52c41a" />
                        </template>
                      </a-button>
                    </a-tooltip>
                    <a-popconfirm title="确认删除该凭证？" @confirm="handleRemoveCredential(c)">
                      <a-button type="text" danger size="small"><template #icon><DeleteOutlined /></template></a-button>
                    </a-popconfirm>
                  </div>
                </div>
                <div class="credential-fields">
                  <div class="cred-field">
                    <span class="cred-label">AppID</span>
                    <span class="cred-value">{{ c.appId || '-' }}</span>
                    <a-button type="text" size="small" @click="copyText(c.appId || '')">
                      <template #icon><CopyOutlined /></template>
                    </a-button>
                  </div>
                  <div class="cred-field">
                    <span class="cred-label">AppSecret</span>
                    <span class="cred-value cred-secret">{{ c.appSecret || '-' }}</span>
                  </div>
                  <div v-if="c.remark" class="cred-field">
                    <span class="cred-label">备注</span>
                    <span class="cred-value">{{ c.remark }}</span>
                  </div>
                </div>
              </div>
            </div>
          </a-spin>
        </div>
      </a-tab-pane>

      <!-- ====== 配置 Tab ====== -->
      <a-tab-pane key="config" tab="应用配置">
        <div class="tab-content">
          <a-button type="primary" @click="goToConfig">
            <template #icon><SettingOutlined /></template>
            配置应用参数
          </a-button>
          <div class="config-tip">
            配置应用的 API、回调、支付、Git 等信息
          </div>
        </div>
      </a-tab-pane>

      <!-- ====== 版本 Tab ====== -->
      <a-tab-pane key="versions" tab="版本管理">
        <div class="tab-content">
          <div class="tab-toolbar">
            <span class="tab-count">共 {{ versions.length }} 个版本</span>
            <a-button type="primary" size="small" @click="showAddVersion = true">
              <template #icon><PlusOutlined /></template>
              新增版本
            </a-button>
          </div>

          <a-spin :spinning="versionsLoading">
            <div v-if="versions.length === 0 && !versionsLoading" class="empty-wrap">
              <a-empty description="暂无版本记录" />
            </div>
            <div v-else class="version-list">
              <div v-for="v in versions" :key="v.id" class="version-item">
                <div class="version-left">
                  <div class="version-no">
                    {{ v.versionNo }}
                    <a-tag v-if="v.isCurrent" color="green" style="margin-left: 6px">当前</a-tag>
                  </div>
                  <div class="version-name">{{ v.versionName || '' }}</div>
                  <div v-if="v.changelog" class="version-changelog">{{ v.changelog }}</div>
                  <div class="version-meta">
                    <span>{{ envText(v.env) }}</span>
                    <span class="meta-dot">·</span>
                    <span>{{ formatDateTime(v.publishTime || v.createTime) }}</span>
                  </div>
                </div>
                <div class="version-right">
                  <a-tag :color="versionStatusColor(v.status)">{{ versionStatusText(v.status) }}</a-tag>
                  <div class="version-actions">
                    <a-popconfirm
                      v-if="!v.isCurrent && v.status !== 0"
                      title="发布此版本为当前运行版本？"
                      @confirm="handlePublish(v)"
                    >
                      <a-button type="link" size="small">发布</a-button>
                    </a-popconfirm>
                    <a-popconfirm
                      v-if="!v.isCurrent && v.status === 1"
                      title="回滚到此版本？"
                      @confirm="handleRollback(v)"
                    >
                      <a-button type="link" size="small">回滚</a-button>
                    </a-popconfirm>
                  </div>
                </div>
              </div>
            </div>
          </a-spin>
        </div>
      </a-tab-pane>

      <!-- ====== 设置 Tab ====== -->
      <a-tab-pane key="settings" tab="应用设置">
        <div class="tab-content">
          <a-spin :spinning="settingsLoading">
            <a-form :model="settingsForm" layout="vertical">
              <!-- 应用图标 -->
              <a-form-item label="应用图标">
                <div class="icon-upload-row">
                  <!-- 预览 / 点击触发上传 -->
                  <a-upload
                    name="file"
                    accept=".jpg,.jpeg,.png,.svg,.bmp"
                    :show-upload-list="false"
                    :before-upload="handleIconBeforeUpload"
                    :custom-request="handleIconUpload"
                  >
                    <div class="icon-preview-wrap icon-preview-clickable" :class="{ 'is-uploading': iconUploading }">
                      <img v-if="settingsForm.icon" :src="settingsForm.icon" class="icon-preview-img" />
                      <div v-else class="icon-preview-placeholder" :style="{ background: iconBgColor(settingsForm.productName) }">
                        {{ (settingsForm.productName || 'A').charAt(0).toUpperCase() }}
                      </div>
                      <!-- 遮罩 hover 提示 -->
                      <div class="icon-preview-mask">
                        <LoadingOutlined v-if="iconUploading" />
                        <UploadOutlined v-else />
                        <span>{{ iconUploading ? '上传中...' : '点击上传' }}</span>
                      </div>
                    </div>
                  </a-upload>

                  <!-- URL 输入（可直接粘贴） -->
                  <div class="icon-url-input">
                    <a-input v-model:value="settingsForm.icon" placeholder="或粘贴图标 URL" allow-clear />
                    <div class="icon-tip">
                      支持 JPEG / PNG / SVG / BMP · 2 MB 以内 · 尺寸 ≥ 240×240 px
                    </div>
                  </div>
                </div>
              </a-form-item>

              <!-- 应用名称 -->
              <a-form-item label="应用名称" required>
                <a-input v-model:value="settingsForm.productName" placeholder="请输入应用名称" show-count :maxlength="50" />
              </a-form-item>

              <!-- 应用标识（只读，创建后不允许修改） -->
              <a-form-item label="应用标识（Code）">
                <a-input :value="settingsForm.productCode" disabled>
                  <template #suffix>
                    <a-tooltip title="应用标识唯一且不可修改">
                      <LockOutlined style="color:#bbb" />
                    </a-tooltip>
                  </template>
                </a-input>
                <div class="icon-tip">应用标识全局唯一，创建后不可修改</div>
              </a-form-item>

              <!-- 应用描述 -->
              <a-form-item label="应用描述">
                <a-textarea
                  v-model:value="settingsForm.description"
                  placeholder="简短描述应用用途，便于团队成员了解"
                  :rows="3"
                  show-count
                  :maxlength="200"
                />
              </a-form-item>

              <!-- 管理后台主页 -->
              <a-form-item label="管理后台主页">
                <a-input
                  v-model:value="settingsForm.adminUrl"
                  placeholder="如：https://admin.example.com"
                  allow-clear
                >
                  <template #prefix><LinkOutlined style="color:#bbb" /></template>
                </a-input>
              </a-form-item>

              <!-- 保存按钮 -->
              <a-form-item>
                <a-button type="primary" :loading="settingsSaving" @click="handleSaveSettings">保存修改</a-button>
              </a-form-item>
            </a-form>

            <!-- 危险操作区 -->
            <a-divider />
            <div class="danger-zone">
              <div class="danger-title">危险操作</div>
              <div class="danger-item">
                <div class="danger-item-info">
                  <div class="danger-item-name">转移应用所有权</div>
                  <div class="danger-item-desc">将应用所有者转让给其他成员，转让后你将失去 owner 权限。</div>
                </div>
                <a-button danger @click="showTransfer = true">转移所有权</a-button>
              </div>
              <a-divider style="margin: 12px 0" />
              <div class="danger-item">
                <div class="danger-item-info">
                  <div class="danger-item-name">删除应用</div>
                  <div class="danger-item-desc">永久删除应用及其所有配置、成员、凭证和版本记录，此操作不可撤销。</div>
                </div>
                <a-popconfirm
                  title=""
                  :open="showDeleteApp"
                  @confirm="handleDeleteApp"
                  @cancel="showDeleteApp = false"
                >
                  <template #description>
                    <div class="delete-confirm-content">
                      <a-alert
                        message="删除后无法恢复，所有数据将被永久清除"
                        type="error"
                        show-icon
                        style="margin-bottom: 12px"
                      />
                      <div class="delete-confirm-label">
                        请输入应用名称 <b>{{ app?.productName }}</b> 以确认删除：
                      </div>
                      <a-input
                        v-model:value="deleteConfirmText"
                        :placeholder="app?.productName || ''"
                        style="margin-top: 6px"
                        @click.stop
                        @press-enter="handleDeleteApp"
                      />
                    </div>
                  </template>
                  <a-button danger type="primary" ghost @click.stop="showDeleteApp = true">删除应用</a-button>
                </a-popconfirm>
              </div>
            </div>
          </a-spin>
        </div>
      </a-tab-pane>

      <!-- ====== 动态 Tab ====== -->
      <a-tab-pane key="events" tab="操作动态">
        <div class="tab-content">
          <a-spin :spinning="eventsLoading">
            <div v-if="events.length === 0 && !eventsLoading" class="empty-wrap">
              <a-empty description="暂无动态记录" />
            </div>
            <a-timeline v-else class="event-timeline">
              <a-timeline-item
                v-for="e in events"
                :key="e.id"
                :color="eventColor(e.eventType)"
              >
                <div class="event-item">
                  <div class="event-title">{{ e.title }}</div>
                  <div v-if="e.content" class="event-content">{{ e.content }}</div>
                  <div class="event-meta">
                    <span v-if="e.operator">{{ e.operator }}</span>
                    <span class="meta-dot">·</span>
                    <span>{{ formatDateTime(e.createTime) }}</span>
                  </div>
                </div>
              </a-timeline-item>
            </a-timeline>
          </a-spin>
        </div>
      </a-tab-pane>
    </a-tabs>
  </a-drawer>

  <!-- 小程序扫码弹窗 -->
  <QrCodeModal
    v-model:open="qrOpen"
    :qrcode-url="qrApp?.qrcode"
    :app-name="qrApp?.productName"
    :title="qrApp ? (APP_TYPE_NAME[qrApp.appType ?? 10] || '小程序') + '二维码' : ''"
    :tip="qrApp ? getScanTip(qrApp.appType ?? 20) : ''"
  />

  <!-- 邀请成员弹窗 - 三种方式 -->
  <a-modal
    v-model:open="showInvite"
    title="邀请成员"
    :footer="null"
    width="560px"
    @cancel="showInvite = false"
  >
    <div class="invite-modal-content">
      <!-- 邀请方式切换 -->
      <a-tabs v-model:active-key="inviteTab" class="invite-tabs">
        <!-- 方式1：手机号邀请 -->
        <a-tab-pane key="phone" tab="手机号邀请">
          <div class="invite-panel">
            <a-alert
              message="输入对方注册手机号，系统将发送邀请通知"
              type="info"
              show-icon
              style="margin-bottom: 16px"
            />
            <a-form :model="inviteForm" layout="vertical">
              <a-form-item label="对方手机号" required>
                <a-input
                  v-model:value="inviteForm.phone"
                  placeholder="请输入11位手机号"
                  allow-clear
                  :maxlength="11"
                >
                  <template #prefix><MobileOutlined style="color: #bbb" /></template>
                </a-input>
              </a-form-item>
              <a-form-item label="邀请角色">
                <a-select v-model:value="inviteForm.role" :options="inviteRoleOptions" />
              </a-form-item>
              <a-form-item>
                <a-button
                  type="primary"
                  :loading="inviteLoading"
                  :disabled="!isValidPhone(inviteForm.phone)"
                  block
                  @click="handleInviteByPhone"
                >
                  发送邀请
                </a-button>
              </a-form-item>
            </a-form>
          </div>
        </a-tab-pane>

        <!-- 方式2：二维码邀请 -->
        <a-tab-pane key="qrcode" tab="扫码邀请">
          <div class="invite-panel qrcode-panel">
            <a-alert
              message="让对方使用微信或浏览器扫描二维码加入"
              type="info"
              show-icon
              style="margin-bottom: 16px"
            />
            <div class="qrcode-section">
              <div v-if="!inviteUrl" class="qrcode-generate">
                <a-form :model="inviteForm" layout="vertical">
                  <a-form-item label="邀请角色">
                    <a-select v-model:value="inviteForm.role" :options="inviteRoleOptions" />
                  </a-form-item>
                  <a-button type="primary" :loading="qrCodeLoading" block @click="onGenerateInviteQrCode">
                    生成邀请二维码
                  </a-button>
                </a-form>
              </div>
              <div v-else class="qrcode-display">
                <div class="qrcode-wrapper">
                  <a-qrcode ref="qrcodeRef" :value="inviteUrl" :size="200" class="qrcode-img" />
                  <div v-if="qrCodeExpired" class="qrcode-overlay">
                    <div class="qrcode-expired">
                      <ClockCircleOutlined style="font-size: 24px; margin-bottom: 8px" />
                      <div>二维码已过期</div>
                      <a-button type="link" size="small" @click="onGenerateInviteQrCode">重新生成</a-button>
                    </div>
                  </div>
                </div>
                <div class="qrcode-info">
                  <div class="qrcode-role">角色：{{ roleText(inviteForm.role) }}</div>
                  <div class="qrcode-expire">有效期：24小时</div>
                </div>
                <a-space>
                  <a-button @click="downloadQrCode">
                    <template #icon><DownloadOutlined /></template>
                    下载二维码
                  </a-button>
                  <a-button type="primary" @click="onGenerateInviteQrCode">
                    <template #icon><ReloadOutlined /></template>
                    重新生成
                  </a-button>
                </a-space>
              </div>
            </div>
          </div>
        </a-tab-pane>

        <!-- 方式3：链接邀请 -->
        <a-tab-pane key="link" tab="链接邀请">
          <div class="invite-panel">
            <a-alert
              message="复制邀请链接发送给对方，点击链接即可加入"
              type="info"
              show-icon
              style="margin-bottom: 16px"
            />
            <div class="link-section">
              <a-form :model="inviteForm" layout="vertical">
                <a-form-item label="邀请角色">
                  <a-select v-model:value="inviteForm.role" :options="inviteRoleOptions" />
                </a-form-item>
              </a-form>
              <div v-if="!inviteLink" class="link-generate">
                <a-button type="primary" :loading="linkLoading" block @click="onGenerateInviteLink">
                  生成邀请链接
                </a-button>
              </div>
              <div v-else class="link-display">
                <div class="link-input-group">
                  <a-input :value="inviteLink" readonly class="link-input">
                    <template #suffix>
                      <a-button type="link" size="small" @click="copyInviteLink">
                        <template #icon><CopyOutlined /></template>
                        复制
                      </a-button>
                    </template>
                  </a-input>
                </div>
                <div class="link-info">
                  <a-tag color="blue">{{ roleText(inviteForm.role) }}</a-tag>
                  <span class="link-expire">有效期 7 天</span>
                </div>
                <a-space style="margin-top: 12px">
                  <a-button @click="onGenerateInviteLink">
                    <template #icon><ReloadOutlined /></template>
                    重新生成
                  </a-button>
                </a-space>
              </div>
            </div>
          </div>
        </a-tab-pane>
      </a-tabs>
    </div>
  </a-modal>

  <!-- 新增凭证弹窗 -->
  <a-modal
    v-model:open="showAddCredential"
    title="新增密钥凭证"
    :confirm-loading="addCredentialLoading"
    @ok="handleAddCredential"
    @cancel="showAddCredential = false"
  >
    <a-alert
      message="凭证创建后 AppSecret 仅展示一次，请及时保存"
      type="warning"
      show-icon
      style="margin-bottom: 16px"
    />
    <a-form :model="credentialForm" layout="vertical">
      <a-form-item label="凭证名称" required>
        <a-input v-model:value="credentialForm.name" placeholder="如：生产环境密钥" />
      </a-form-item>
      <a-form-item label="凭证类型">
        <a-select v-model:value="credentialForm.type">
          <a-select-option value="server">服务端（server）</a-select-option>
          <a-select-option value="client">客户端（client）</a-select-option>
          <a-select-option value="webhook">Webhook</a-select-option>
        </a-select>
      </a-form-item>
      <a-form-item label="备注">
        <a-input v-model:value="credentialForm.remark" placeholder="可选备注" />
      </a-form-item>
    </a-form>
  </a-modal>

  <!-- 新建凭证成功弹窗（展示明文 secret） -->
  <a-modal
    v-model:open="showSecretResult"
    title="凭证创建成功"
    :footer="null"
    :closable="false"
    :mask-closable="false"
    width="520px"
  >
    <a-alert
      message="AppSecret 仅此一次展示，请立即复制保存，关闭后无法再次查看！"
      type="error"
      show-icon
      style="margin-bottom: 16px"
    />
    <a-descriptions :column="1" bordered size="small">
      <a-descriptions-item label="AppID">
        <div class="secret-row">
          <span>{{ newCredentialResult?.appId }}</span>
          <a-button type="link" size="small" @click="copyText(newCredentialResult?.appId || '')">复制</a-button>
        </div>
      </a-descriptions-item>
      <a-descriptions-item label="AppSecret">
        <div class="secret-row">
          <span class="secret-text">{{ newCredentialResult?.appSecret }}</span>
          <a-button type="link" size="small" @click="copyText(newCredentialResult?.appSecret || '')">复制</a-button>
        </div>
      </a-descriptions-item>
    </a-descriptions>
    <div style="margin-top: 16px; text-align: right">
      <a-button type="primary" @click="showSecretResult = false">我已保存，关闭</a-button>
    </div>
  </a-modal>

  <!-- 重置密钥成功弹窗 -->
  <a-modal
    v-model:open="showResetResult"
    title="密钥重置成功"
    :footer="null"
    :closable="false"
    :mask-closable="false"
    width="520px"
  >
    <a-alert
      message="新 AppSecret 仅此一次展示，请立即复制保存！"
      type="error"
      show-icon
      style="margin-bottom: 16px"
    />
    <a-descriptions :column="1" bordered size="small">
      <a-descriptions-item label="AppSecret">
        <div class="secret-row">
          <span class="secret-text">{{ resetSecretResult?.appSecret }}</span>
          <a-button type="link" size="small" @click="copyText(resetSecretResult?.appSecret || '')">复制</a-button>
        </div>
      </a-descriptions-item>
    </a-descriptions>
    <div style="margin-top: 16px; text-align: right">
      <a-button type="primary" @click="showResetResult = false">我已保存，关闭</a-button>
    </div>
  </a-modal>

  <!-- 转移所有权弹窗 -->
  <a-modal
    v-model:open="showTransfer"
    title="转移应用所有权"
    :confirm-loading="transferLoading"
    ok-text="确认转移"
    ok-type="danger"
    @ok="handleTransfer"
    @cancel="showTransfer = false"
  >
    <a-alert
      message="转移后你将失去 owner 权限，此操作不可撤销，请谨慎操作！"
      type="warning"
      show-icon
      style="margin-bottom: 16px"
    />
    <a-form :model="transferForm" layout="vertical">
      <a-form-item label="转让给（选择成员）" required>
        <a-select
          v-model:value="transferForm.userId"
          placeholder="请选择要转让的成员"
          style="width: 100%"
          :options="transferableMembers"
        />
      </a-form-item>
    </a-form>
  </a-modal>

  <!-- 新增版本弹窗 -->
  <a-modal
    v-model:open="showAddVersion"
    title="新增版本"
    :confirm-loading="addVersionLoading"
    @ok="handleAddVersion"
    @cancel="showAddVersion = false"
  >
    <a-form :model="versionForm" layout="vertical" style="margin-top: 8px">
      <a-form-item label="版本号" required>
        <a-input v-model:value="versionForm.versionNo" placeholder="如：1.0.0" />
      </a-form-item>
      <a-form-item label="版本名称">
        <a-input v-model:value="versionForm.versionName" placeholder="如：正式版 1.0" />
      </a-form-item>
      <a-form-item label="环境">
        <a-select v-model:value="versionForm.env">
          <a-select-option value="production">生产（production）</a-select-option>
          <a-select-option value="staging">预发（staging）</a-select-option>
          <a-select-option value="development">开发（development）</a-select-option>
        </a-select>
      </a-form-item>
      <a-form-item label="更新说明">
        <a-textarea v-model:value="versionForm.changelog" :rows="3" placeholder="本次版本更新内容..." />
      </a-form-item>
    </a-form>
  </a-modal>
</template>

<script setup lang="ts">
import {
  CloseOutlined,
  PlusOutlined,
  ReloadOutlined,
  CopyOutlined,
  DeleteOutlined,
  StopOutlined,
  CheckCircleOutlined,
  LinkOutlined,
  UploadOutlined,
  LoadingOutlined,
  LockOutlined,
  SettingOutlined,
  MobileOutlined,
  ClockCircleOutlined,
  DownloadOutlined,
  QrcodeOutlined,
  GlobalOutlined,
} from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import type { AppProduct } from '@/api/cms/cmsWebsite/model'
import type { AppUser } from '@/api/app/appUser/model'
import type { AppCredential } from '@/api/app/appCredential/model'
import type { AppVersion } from '@/api/app/appVersion/model'
import type { AppEvent } from '@/api/app/appEvent/model'
import { APP_TYPE_NAME } from '@/api/app/appProduct/model'
import { getAppEntries, executeEntry, getScanTip } from '@/utils/appEntry'
import type { AppEntry } from '@/utils/appEntry'
import QrCodeModal from '@/components/QrCodeModal.vue'
import {
  listAppUser,
  inviteAppUser,
  updateAppUserRole,
  removeAppUser,
} from '@/api/app/appUser'
import {
  listAppCredential,
  createAppCredential,
  resetAppCredentialSecret,
  updateAppCredentialStatus,
  removeAppCredential,
} from '@/api/app/appCredential'
import {
  listAppVersion,
  addAppVersion,
  publishAppVersion,
  rollbackAppVersion,
} from '@/api/app/appVersion'
import { listAppEvent, addAppEvent } from '@/api/app/appEvent'
import { removeAppProduct, updateAppProduct } from '@/api/app/appProduct'
import { uploadFile } from '@/api/system/file'
import { generateInviteQrCode, generateInviteLink } from '@/api/app/invite'

const props = defineProps<{
  open: boolean
  app: AppProduct | null
}>()

const emit = defineEmits<{
  'update:open': [value: boolean]
  'updated': [app: AppProduct]
  'deleted': []
}>()

const activeTab = ref('members')

// =================== 应用图标上传 ===================
const iconUploading = ref(false)
const ALLOWED_ICON_TYPES = ['image/jpeg', 'image/png', 'image/svg+xml', 'image/bmp']
const MAX_ICON_SIZE = 2 * 1024 * 1024 // 2 MB
const MIN_ICON_DIM = 240 // px

/** beforeUpload：格式 + 大小 + 分辨率校验 */
function handleIconBeforeUpload(file: File): Promise<boolean> {
  return new Promise((resolve, reject) => {
    // 格式校验
    if (!ALLOWED_ICON_TYPES.includes(file.type)) {
      message.error('仅支持 JPEG / PNG / SVG / BMP 格式')
      return reject(false)
    }
    // 大小校验
    if (file.size > MAX_ICON_SIZE) {
      message.error('图标文件不能超过 2 MB')
      return reject(false)
    }
    // SVG 不检测像素尺寸（矢量图）
    if (file.type === 'image/svg+xml') return resolve(true)
    // 分辨率校验
    const url = URL.createObjectURL(file)
    const img = new Image()
    img.onload = () => {
      URL.revokeObjectURL(url)
      if (img.width < MIN_ICON_DIM || img.height < MIN_ICON_DIM) {
        message.error(`图标尺寸不能小于 ${MIN_ICON_DIM}×${MIN_ICON_DIM} px（当前 ${img.width}×${img.height}）`)
        return reject(false)
      }
      resolve(true)
    }
    img.onerror = () => { URL.revokeObjectURL(url); resolve(true) }
    img.src = url
  })
}

/** customRequest：上传后把 url 写入 settingsForm.icon */
async function handleIconUpload({ file }: { file: File }) {
  iconUploading.value = true
  try {
    const result = await uploadFile(file)
    settingsForm.icon = result.url || ''
    message.success('图标上传成功')
  } catch (e: any) {
    message.error(e.message || '图标上传失败')
  } finally {
    iconUploading.value = false
  }
}

// =================== 应用设置 ===================
const settingsLoading = ref(false)
const settingsSaving = ref(false)
const settingsForm = reactive({
  productId: undefined as number | undefined,
  productName: '',
  productCode: '',
  icon: '',
  description: '',
  adminUrl: '',
})

function initSettings() {
  if (!props.app) return
  settingsForm.productId = props.app.productId
  settingsForm.productName = props.app.productName || ''
  settingsForm.productCode = props.app.productCode || ''
  settingsForm.icon = props.app.icon || ''
  settingsForm.description = props.app.description || ''
  settingsForm.adminUrl = props.app.adminUrl || ''
}

async function handleSaveSettings() {
  if (!settingsForm.productName) { message.warning('应用名称不能为空'); return }
  settingsSaving.value = true
  try {
    // productCode 唯一且不可修改，提交时明确排除
    const { productCode: _omit, ...submitData } = settingsForm
    await updateAppProduct(submitData as AppProduct)
    message.success('保存成功')
    logEvent('settings', '修改了应用设置', `应用名称：${submitData.productName}`)
    emit('updated', { ...props.app, ...submitData } as AppProduct)
  } catch (e: any) {
    message.error(e.message || '保存失败')
  } finally {
    settingsSaving.value = false
  }
}

// =================== 删除应用 ===================
const showDeleteApp = ref(false)
const deleteAppLoading = ref(false)
const deleteConfirmText = ref('')

async function handleDeleteApp() {
  if (!props.app?.productId) return
  if (deleteConfirmText.value !== props.app.productName) {
    message.warning('请输入正确的应用名称以确认删除')
    return
  }
  deleteAppLoading.value = true
  try {
    await removeAppProduct(props.app.productId!)
    message.success('应用已删除')
    emit('update:open', false)
    emit('deleted')
  } catch (e: any) {
    message.error(e.message || '删除失败')
  } finally {
    deleteAppLoading.value = false
  }
}

// =================== 转移所有权 ===================
const showTransfer = ref(false)
const transferLoading = ref(false)
const transferForm = reactive({ userId: undefined as number | undefined })

const transferableMembers = computed(() =>
  members.value
    .filter(m => m.role !== 'owner')
    .map(m => ({
      label: m.nickname || m.username || `用户 ${m.userId}`,
      value: m.userId,
    }))
)

async function handleTransfer() {
  if (!transferForm.userId) { message.warning('请选择转让对象'); return }
  transferLoading.value = true
  try {
    // 1. 将目标成员设为 owner
    const target = members.value.find(m => m.userId === transferForm.userId)
    if (!target) throw new Error('成员不存在')
    await updateAppUserRole(target.id!, 'owner')
    // 2. 将当前 owner 降为 admin
    const currentOwner = members.value.find(m => m.role === 'owner')
    if (currentOwner) await updateAppUserRole(currentOwner.id!, 'admin')
    // 3. 同步更新 cms_website.user_id，确保应用归属正确
    if (props.app?.productId) {
      await updateAppProduct({
        productId: props.app.productId,
        userId: transferForm.userId,
      } as AppProduct)
      // 通知父组件刷新列表（userId 已变更）
      emit('updated', { ...props.app, userId: transferForm.userId } as AppProduct)
    }
    message.success('所有权转移成功')
    logEvent('transfer', '转移了应用所有权')
    showTransfer.value = false
    transferForm.userId = undefined
    await loadMembers()
  } catch (e: any) {
    message.error(e.message || '转移失败')
  } finally {
    transferLoading.value = false
  }
}

// =================== 成员 ===================
const members = ref<AppUser[]>([])
const membersLoading = ref(false)
const showInvite = ref(false)

// 邀请弹窗相关
const inviteTab = ref('phone')
const inviteLoading = ref(false)
const inviteForm = reactive({ phone: '', role: 'developer' })

// 邀请权限控制：获取当前用户在应用中的角色
const currentUserId = computed(() => {
  if (import.meta.client) {
    return Number(localStorage.getItem('UserId')) || 0
  }
  return 0
})

const myRoleInApp = computed(() => {
  const myMember = members.value.find(m => m.userId === currentUserId.value)
  return myMember?.role || ''
})

// 是否可以邀请管理员（仅 owner 和 admin 可邀请任意角色）
const canInviteAdmin = computed(() => {
  return myRoleInApp.value === 'owner' || myRoleInApp.value === 'admin'
})

// 根据权限获取可邀请的角色列表
const inviteRoleOptions = computed(() => {
  const options = [
    { value: 'developer', label: '开发者' }
  ]
  if (canInviteAdmin.value) {
    options.unshift({ value: 'admin', label: '管理员' })
    options.push({ value: 'viewer', label: '只读' })
  }
  return options
})

// 监听弹窗打开，重置状态并检查权限
watch(showInvite, (val) => {
  if (val) {
    inviteTab.value = 'phone'
    inviteForm.phone = ''
    // 默认角色：开发者可邀请 developer，管理员可邀请任意
    inviteForm.role = canInviteAdmin.value ? 'developer' : 'developer'
    inviteUrl.value = ''
    inviteLink.value = ''
    qrCodeExpired.value = false
    clearInviteTimers()
  } else {
    clearInviteTimers()
  }
})
const qrCodeLoading = ref(false)
const inviteUrl = ref('')
const qrCodeExpired = ref(false)
const qrcodeRef = ref()  // a-qrcode 组件 ref
const qrCodeExpireTimer = ref<NodeJS.Timeout | null>(null)

// 链接邀请
const linkLoading = ref(false)
const inviteLink = ref('')
const linkExpireTimer = ref<NodeJS.Timeout | null>(null)

// 监听弹窗打开，重置状态
watch(showInvite, (val) => {
  if (val) {
    inviteTab.value = 'phone'
    inviteForm.phone = ''
    inviteForm.role = 'developer'
    inviteUrl.value = ''
    inviteLink.value = ''
    qrCodeExpired.value = false
    clearInviteTimers()
  } else {
    clearInviteTimers()
  }
})

function clearInviteTimers() {
  if (qrCodeExpireTimer.value) {
    clearTimeout(qrCodeExpireTimer.value)
    qrCodeExpireTimer.value = null
  }
  if (linkExpireTimer.value) {
    clearTimeout(linkExpireTimer.value)
    linkExpireTimer.value = null
  }
}

function isValidPhone(phone: string): boolean {
  return /^1[3-9]\d{9}$/.test(phone)
}

function roleText(role: string): string {
  const map: Record<string, string> = {
    owner: '所有者',
    admin: '管理员',
    developer: '开发者',
    viewer: '只读',
  }
  return map[role] || role
}

async function loadMembers() {
  if (!props.app?.productId) return
  membersLoading.value = true
  try {
    const res = await listAppUser({ appId: props.app.productId })
    // 按角色权限排序：owner > admin > developer > viewer
    const roleOrder: Record<string, number> = { owner: 0, admin: 1, developer: 2, viewer: 3 }
    members.value = (res || []).sort((a, b) => {
      const orderA = roleOrder[a.role || ''] ?? 99
      const orderB = roleOrder[b.role || ''] ?? 99
      return orderA - orderB
    })
  } catch (e: any) {
    message.error(e.message || '加载成员失败')
  } finally {
    membersLoading.value = false
  }
}

// ========== 方式1：手机号邀请 ==========
async function handleInviteByPhone() {
  const phone = inviteForm.phone?.trim()
  if (!isValidPhone(phone)) {
    message.warning('请输入有效的11位手机号')
    return
  }

  inviteLoading.value = true
  try {
    await inviteAppUser({
      appId: props.app!.productId!,
      phone,
      role: inviteForm.role,
    })
    message.success('邀请已发送')
    logEvent('member_invite', '邀请了新成员', `方式：手机号 角色：${inviteForm.role}`)
    showInvite.value = false
    inviteForm.phone = ''
    await loadMembers()
  } catch (e: any) {
    message.error(e.message || '邀请失败')
  } finally {
    inviteLoading.value = false
  }
}

// ========== 方式2：二维码邀请 ==========
async function onGenerateInviteQrCode() {
  if (!props.app?.productId) return

  qrCodeLoading.value = true
  try {
    // 调用后端接口生成邀请 token（不再依赖后端返回二维码图片）
    const res = await generateInviteQrCode({
      appId: props.app.productId,
      role: inviteForm.role,
    })

    if (res.data?.code === 0 && res.data.data) {
      // 使用后端返回的邀请链接，前端使用 <a-qrcode> 生成二维码
      inviteUrl.value = res.data.data.inviteUrl || res.data.data.qrCodeUrl
      qrCodeExpired.value = false

      // 设置24小时过期提示（实际过期由后端控制）
      if (qrCodeExpireTimer.value) clearTimeout(qrCodeExpireTimer.value)
      qrCodeExpireTimer.value = setTimeout(() => {
        qrCodeExpired.value = true
      }, 24 * 60 * 60 * 1000)

      message.success('二维码生成成功')
    } else {
      throw new Error(res.data?.message || '生成失败')
    }
  } catch (e: any) {
    // 如果后端接口不存在，使用本地生成邀请链接作为降级方案
    generateFallbackInviteUrl()
  } finally {
    qrCodeLoading.value = false
  }
}

// 降级方案：本地生成邀请链接
function generateFallbackInviteUrl() {
  if (!props.app?.productId) return

  // 生成邀请链接（本地模式）
  const baseUrl = window.location.origin
  const token = generateInviteToken()
  inviteUrl.value = `${baseUrl}/invite/accept?token=${token}&appId=${props.app.productId}&role=${inviteForm.role}`

  // 存储 token 到 localStorage（实际应该由后端存储）
  const inviteData = {
    appId: props.app.productId,
    role: inviteForm.role,
    createTime: Date.now(),
    expireTime: Date.now() + 24 * 60 * 60 * 1000,
  }
  localStorage.setItem(`invite_token_${token}`, JSON.stringify(inviteData))

  qrCodeExpired.value = false
  if (qrCodeExpireTimer.value) clearTimeout(qrCodeExpireTimer.value)
  qrCodeExpireTimer.value = setTimeout(() => {
    qrCodeExpired.value = true
  }, 24 * 60 * 60 * 1000)

  message.success('二维码已生成（本地模式）')
}

function generateInviteToken(): string {
  return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
}

// 下载二维码
function downloadQrCode() {
  if (!inviteUrl.value) return

  // 尝试从 a-qrcode 组件获取 canvas 进行下载
  const qrcodeEl = qrcodeRef.value?.$el as HTMLElement
  if (qrcodeEl) {
    const canvas = qrcodeEl.querySelector('canvas')
    if (canvas) {
      const link = document.createElement('a')
      link.href = canvas.toDataURL('image/png')
      link.download = `invite-${props.app?.productCode || 'app'}.png`
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      return
    }
  }

  // 降级：下载邀请链接文本
  const link = document.createElement('a')
  link.href = inviteUrl.value
  link.download = `invite-${props.app?.productCode || 'app'}-link.txt`
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
}

// ========== 方式3：链接邀请 ==========
async function onGenerateInviteLink() {
  if (!props.app?.productId) return

  linkLoading.value = true
  try {
    // 调用后端接口生成邀请链接
    const res = await generateInviteLink({
      appId: props.app.productId,
      role: inviteForm.role,
    })

    if (res.data?.code === 0 && res.data.data?.inviteUrl) {
      // 将 websopy.com 替换为 websopy.websoft.top（临时方案，直到正式域名上线）
      inviteLink.value = res.data.data.inviteUrl.replace('https://websopy.com', 'https://websopy.websoft.top')

      // 设置7天过期提示
      if (linkExpireTimer.value) clearTimeout(linkExpireTimer.value)
      linkExpireTimer.value = setTimeout(() => {
        inviteLink.value = ''
      }, 7 * 24 * 60 * 60 * 1000)

      message.success('邀请链接已生成')
    } else {
      throw new Error(res.data?.message || '生成失败')
    }
  } catch (e: any) {
    // 降级方案：前端生成链接
    generateFallbackLink()
  } finally {
    linkLoading.value = false
  }
}

function generateFallbackLink() {
  if (!props.app?.productId) return

  const baseUrl = window.location.origin
  const token = generateInviteToken()
  const inviteUrl = `${baseUrl}/invite/accept?token=${token}&appId=${props.app.productId}&role=${inviteForm.role}`

  inviteLink.value = inviteUrl

  // 存储 token
  const inviteData = {
    appId: props.app.productId,
    role: inviteForm.role,
    createTime: Date.now(),
    expireTime: Date.now() + 7 * 24 * 60 * 60 * 1000,
  }
  localStorage.setItem(`invite_token_${token}`, JSON.stringify(inviteData))

  if (linkExpireTimer.value) clearTimeout(linkExpireTimer.value)
  linkExpireTimer.value = setTimeout(() => {
    inviteLink.value = ''
  }, 7 * 24 * 60 * 60 * 1000)

  message.success('邀请链接已生成（本地模式）')
}

function copyInviteLink() {
  if (!inviteLink.value) return
  navigator.clipboard.writeText(inviteLink.value).then(() => {
    message.success('链接已复制到剪贴板')
  })
}

async function handleRoleChange(m: AppUser, role: string) {
  try {
    await updateAppUserRole(m.id!, role)
    message.success('角色已更新')
    logEvent('member_role', '修改了成员角色', `${m.nickname || m.username} → ${role}`)
    m.role = role
  } catch (e: any) {
    message.error(e.message || '更新失败')
  }
}

async function handleRemoveMember(m: AppUser) {
  try {
    await removeAppUser(m.id!)
    message.success('已移除')
    logEvent('member_remove', '移除了成员', m.nickname || m.username || String(m.userId))
    await loadMembers()
  } catch (e: any) {
    message.error(e.message || '移除失败')
  }
}

// =================== 密钥 ===================
const credentials = ref<AppCredential[]>([])
const credentialsLoading = ref(false)
const showAddCredential = ref(false)
const addCredentialLoading = ref(false)
const credentialForm = reactive({ name: '', type: 'server', remark: '' })
const showSecretResult = ref(false)
const newCredentialResult = ref<AppCredential | null>(null)
const showResetResult = ref(false)
const resetSecretResult = ref<AppCredential | null>(null)

async function loadCredentials() {
  if (!props.app?.productId) return
  credentialsLoading.value = true
  try {
    const res = await listAppCredential({ appId: props.app.productId })
    credentials.value = res || []
  } catch (e: any) {
    message.error(e.message || '加载凭证失败')
  } finally {
    credentialsLoading.value = false
  }
}

async function handleAddCredential() {
  if (!credentialForm.name) { message.warning('请填写凭证名称'); return }
  addCredentialLoading.value = true
  try {
    const result = await createAppCredential({
      appId: props.app!.productId,
      name: credentialForm.name,
      type: credentialForm.type,
      remark: credentialForm.remark,
    })
    const credentialName = credentialForm.name
    showAddCredential.value = false
    credentialForm.name = ''
    credentialForm.remark = ''
    newCredentialResult.value = result.data || null
    logEvent('credential_create', '创建了访问凭证', credentialName || undefined)
    showSecretResult.value = true
    await loadCredentials()
  } catch (e: any) {
    message.error(e.message || '创建失败')
  } finally {
    addCredentialLoading.value = false
  }
}

async function handleResetSecret(c: AppCredential) {
  try {
    const result = await resetAppCredentialSecret(c.id!)
    resetSecretResult.value = result.data || null
    logEvent('credential_reset', '重置了凭证密钥', c.name || undefined)
    showResetResult.value = true
    await loadCredentials()
  } catch (e: any) {
    message.error(e.message || '重置失败')
  }
}

async function handleToggleCredentialStatus(c: AppCredential) {
  const newStatus = c.status === 0 ? 1 : 0
  try {
    await updateAppCredentialStatus(c.id!, newStatus)
    message.success(newStatus === 0 ? '已启用' : '已冻结')
    logEvent('credential_status', newStatus === 0 ? '启用了访问凭证' : '冻结了访问凭证', c.name || undefined)
    c.status = newStatus
  } catch (e: any) {
    message.error(e.message || '操作失败')
  }
}

async function handleRemoveCredential(c: AppCredential) {
  try {
    await removeAppCredential(c.id!)
    message.success('已删除')
    logEvent('credential_delete', '删除了访问凭证', c.name || undefined)
    await loadCredentials()
  } catch (e: any) {
    message.error(e.message || '删除失败')
  }
}

// =================== 版本 ===================
const versions = ref<AppVersion[]>([])
const versionsLoading = ref(false)
const showAddVersion = ref(false)
const addVersionLoading = ref(false)
const versionForm = reactive({ versionNo: '', versionName: '', env: 'production', changelog: '' })

async function loadVersions() {
  if (!props.app?.productId) return
  versionsLoading.value = true
  try {
    const res = await listAppVersion({ appId: props.app.productId })
    versions.value = res || []
  } catch (e: any) {
    message.error(e.message || '加载版本失败')
  } finally {
    versionsLoading.value = false
  }
}

async function handleAddVersion() {
  if (!versionForm.versionNo) { message.warning('请填写版本号'); return }
  addVersionLoading.value = true
  try {
    await addAppVersion({
      appId: props.app!.productId,
      ...versionForm,
    })
    message.success('创建成功')
    showAddVersion.value = false
    versionForm.versionNo = ''
    versionForm.versionName = ''
    versionForm.changelog = ''
    await loadVersions()
  } catch (e: any) {
    message.error(e.message || '创建失败')
  } finally {
    addVersionLoading.value = false
  }
}

async function handlePublish(v: AppVersion) {
  try {
    await publishAppVersion(v.id!)
    message.success('发布成功')
    logEvent('version_publish', '发布了新版本', v.versionNo || undefined)
    await loadVersions()
  } catch (e: any) {
    message.error(e.message || '发布失败')
  }
}

async function handleRollback(v: AppVersion) {
  try {
    await rollbackAppVersion(v.id!)
    message.success('回滚成功')
    logEvent('version_rollback', '回滚了版本', v.versionNo || undefined)
    await loadVersions()
  } catch (e: any) {
    message.error(e.message || '回滚失败')
  }
}

// =================== 动态 ===================
const events = ref<AppEvent[]>([])
const eventsLoading = ref(false)

async function loadEvents() {
  if (!props.app?.productId) return
  eventsLoading.value = true
  try {
    const res = await listAppEvent({ appId: props.app.productId, limit: 50 })
    events.value = res || []
  } catch (e: any) {
    message.error(e.message || '加载动态失败')
  } finally {
    eventsLoading.value = false
  }
}

// =================== 监听 Tab 变化 + 抽屉打开 ===================
watch(() => props.open, (val) => {
  if (val) {
    activeTab.value = 'members'
    initSettings()
    loadMembers()
  }
})

watch(activeTab, (tab) => {
  if (!props.app?.productId) return
  if (tab === 'members') loadMembers()
  else if (tab === 'credentials') loadCredentials()
  else if (tab === 'versions') loadVersions()
  else if (tab === 'events') loadEvents()
  else if (tab === 'settings') initSettings()
})

// =================== 工具函数 ===================

/** 记录操作动态（静默，不影响主流程） */
function logEvent(eventType: string, title: string, content?: string) {
  if (!props.app?.productId) return
  addAppEvent({
    appId: props.app.productId,
    eventType,
    title,
    content: content || undefined,
  })
}

/** 跳转到配置页面 */
function goToConfig() {
  navigateTo(`/developer/config/${props.app?.productId}`)
}

function copyText(text: string) {
  if (!text) return
  navigator.clipboard.writeText(text).then(() => message.success('已复制'))
}

function statusText(status?: number) {
  const map: Record<number, string> = { 0: '未开通', 1: '已启用', 2: '维护中', 3: '已关闭', 4: '欠费停机', 5: '违规关停' }
  return typeof status === 'number' ? (map[status] ?? '未知') : '-'
}

function envText(env?: string) {
  const map: Record<string, string> = { production: '生产', staging: '预发', development: '开发' }
  return env ? (map[env] ?? env) : '-'
}

function versionStatusText(status?: number) {
  const map: Record<number, string> = { 0: '构建中', 1: '已发布', 2: '已回滚', 3: '构建失败' }
  return typeof status === 'number' ? (map[status] ?? '未知') : '-'
}

function versionStatusColor(status?: number) {
  const map: Record<number, string> = { 0: 'processing', 1: 'success', 2: 'warning', 3: 'error' }
  return typeof status === 'number' ? (map[status] ?? 'default') : 'default'
}

function eventColor(eventType?: string) {
  const map: Record<string, string> = {
    created: 'blue', published: 'green', updated: 'cyan',
    domain_bound: 'purple', member_added: 'orange', status_changed: 'red',
  }
  return eventType ? (map[eventType] ?? 'gray') : 'gray'
}

function formatDateTime(dateStr?: string) {
  if (!dateStr) return '-'
  return dateStr.slice(0, 16).replace('T', ' ')
}

const PALETTE = ['#4e6ef2', '#f4a261', '#e76f51', '#2a9d8f', '#e9c46a', '#457b9d', '#a8dadc', '#f77f00']
function iconBgColor(name?: string) {
  if (!name) return PALETTE[0]
  let h = 0
  for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) & 0xffffffff
  return PALETTE[Math.abs(h) % PALETTE.length]
}

// ====== 入口处理 ======
const qrOpen = ref(false)
const qrApp = ref<AppProduct | null>(null)

function handleEntryClick(entry: AppEntry) {
  if (!props.app) return
  if (entry.type === 'scan-qr') {
    qrApp.value = props.app
    qrOpen.value = true
    return
  }
  executeEntry(entry)
}
</script>

<style scoped>
/* 头部 */
.detail-header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 20px 24px 16px;
  border-bottom: 1px solid #f0f0f0;
  position: relative;
}

.header-icon-wrap {
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.header-icon-img { width: 100%; height: 100%; object-fit: cover; }

.header-icon-placeholder {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; color: #fff;
}

.header-info { flex: 1; min-width: 0; }
.header-title { font-size: 18px; font-weight: 600; color: #1a1a1a; }
.header-sub { display: flex; align-items: center; gap: 8px; margin-top: 4px; }
.header-code { font-size: 12px; color: #999; background: #f5f5f5; padding: 1px 8px; border-radius: 4px; }

.header-status {
  font-size: 12px; padding: 1px 8px; border-radius: 12px; font-weight: 500;
}
.status-1 { background: #e6f7ee; color: #389e0d; }
.status-0 { background: #f5f5f5; color: #999; }
.status-2 { background: #fff7e6; color: #d46b08; }
.status-3, .status-5 { background: #fff1f0; color: #cf1322; }

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex-shrink: 0;
}

.close-btn { position: absolute; top: 16px; right: 16px; }

/* Tabs */
.detail-tabs :deep(.ant-tabs-nav) { padding: 0 24px; margin-bottom: 0; }

/* Tab 内容 */
.tab-content { padding: 16px 24px; min-height: 300px; }

.tab-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 14px;
}

.tab-count { font-size: 13px; color: #999; }

.empty-wrap { display: flex; justify-content: center; padding: 40px 0; }

/* 成员列表 */
.member-list { display: flex; flex-direction: column; gap: 2px; }
.member-item {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 8px;
  transition: background 0.15s;
}
.member-item:hover { background: #fafafa; }
.member-item.pending {
  background: #fffbe6;
  border: 1px dashed #ffd666;
}
.member-item.pending:hover {
  background: #fff7d0;
}
.member-avatar { flex-shrink: 0; background: #4e6ef2; }
.member-info { flex: 1; min-width: 0; }
.member-name {
  font-size: 14px;
  font-weight: 500;
  color: #1a1a1a;
  display: flex;
  align-items: center;
  gap: 8px;
}
.member-sub {
  font-size: 12px;
  color: #999;
  display: flex;
  align-items: center;
  gap: 8px;
}
.member-sub .expire-hint {
  color: #faad14;
}
.member-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/* 密钥列表 */
.credential-list { display: flex; flex-direction: column; gap: 12px; }
.credential-item {
  border: 1px solid #f0f0f0; border-radius: 8px; padding: 14px 16px;
  background: #fafcff;
}
.credential-header {
  display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
}
.credential-name { font-weight: 600; font-size: 14px; flex: 1; }
.credential-actions { margin-left: auto; display: flex; gap: 4px; }
.credential-fields { display: flex; flex-direction: column; gap: 6px; }
.cred-field { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.cred-label { color: #999; width: 70px; flex-shrink: 0; }
.cred-value { font-family: 'Courier New', monospace; color: #333; flex: 1; }
.cred-secret { color: #888; letter-spacing: 1px; }

/* 版本列表 */
.version-list { display: flex; flex-direction: column; gap: 2px; }
.version-item {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 12px; border-radius: 8px;
  border-bottom: 1px solid #f5f5f5;
}
.version-item:last-child { border-bottom: none; }
.version-left { flex: 1; }
.version-no { font-size: 15px; font-weight: 600; color: #1a1a1a; display: flex; align-items: center; }
.version-name { font-size: 13px; color: #666; margin-top: 2px; }
.version-changelog { font-size: 12px; color: #999; margin-top: 4px; max-width: 380px; white-space: pre-wrap; }
.version-meta { font-size: 12px; color: #bbb; margin-top: 4px; display: flex; gap: 4px; }
.version-right { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex-shrink: 0; }
.version-actions { display: flex; gap: 4px; }

/* 动态时间线 */
.event-timeline { padding: 8px 0; }
.event-item { padding-bottom: 4px; }
.event-title { font-size: 14px; font-weight: 500; color: #1a1a1a; }
.event-content { font-size: 13px; color: #666; margin-top: 2px; }
.event-meta { font-size: 12px; color: #bbb; margin-top: 4px; display: flex; gap: 4px; }

/* 密钥明文展示 */
.secret-row { display: flex; align-items: center; gap: 8px; }
.secret-text { font-family: 'Courier New', monospace; font-size: 13px; color: #e03; word-break: break-all; }

.meta-dot { color: #ddd; }

/* 配置提示 */
.config-tip {
  font-size: 13px;
  color: #999;
  margin-top: 12px;
}

/* 图标上传行 */
.icon-upload-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}
.icon-preview-wrap {
  flex-shrink: 0;
  width: 80px;
  height: 80px;
  border-radius: 0; /* 无圆角 */
  overflow: hidden;
  border: 1px dashed #d9d9d9;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background: #fafafa;
  transition: border-color 0.2s;
}
.icon-preview-clickable {
  cursor: pointer;
}
.icon-preview-clickable:hover {
  border-color: #1677ff;
}
.icon-preview-clickable:hover .icon-preview-mask {
  opacity: 1;
}
.icon-preview-img { width: 100%; height: 100%; object-fit: cover; }
.icon-preview-placeholder {
  width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: 28px; font-weight: 700; color: #fff;
}
.icon-preview-mask {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  color: #fff;
  font-size: 12px;
  opacity: 0;
  transition: opacity 0.2s;
}
.icon-preview-mask .anticon { font-size: 18px; }
.is-uploading .icon-preview-mask { opacity: 1; }
.icon-url-input { flex: 1; }
.icon-tip { font-size: 12px; color: #bbb; margin-top: 6px; }

/* 危险区 */
.danger-zone {
  border: 1px solid #ffd6d6;
  border-radius: 8px;
  padding: 16px 20px;
  background: #fffafa;
}
.danger-title {
  font-size: 13px;
  font-weight: 600;
  color: #cf1322;
  margin-bottom: 12px;
}
.danger-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.danger-item-name { font-size: 14px; font-weight: 500; color: #1a1a1a; }
.danger-item-desc { font-size: 12px; color: #999; margin-top: 2px; }

/* 删除确认弹窗 */
.delete-confirm-content {
  max-width: 320px;
}
.delete-confirm-label {
  font-size: 13px;
  color: #333;
  line-height: 1.6;
}

/* 邀请弹窗样式 */
.invite-modal-content {
  padding: 8px 0;
}

.invite-tabs :deep(.ant-tabs-nav) {
  margin-bottom: 16px;
}

.invite-panel {
  padding: 0 4px;
}

.qrcode-panel {
  text-align: center;
}

.qrcode-section {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.qrcode-wrapper {
  position: relative;
  width: 200px;
  height: 200px;
  margin: 16px auto;
  border-radius: 8px;
  background: #fff;
}

.qrcode-img {
  background: #fff;
  border-radius: 8px;
}

.qrcode-overlay {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}

.qrcode-expired {
  text-align: center;
  color: #999;
}

.qrcode-info {
  margin: 12px 0 16px;
  font-size: 13px;
  color: #666;
}

.qrcode-role {
  margin-bottom: 4px;
}

.qrcode-expire {
  color: #999;
  font-size: 12px;
}

.link-section {
  padding: 0 4px;
}

.link-input-group {
  margin-bottom: 12px;
}

.link-input :deep(.ant-input) {
  background: #f5f5f5;
}

.link-info {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.link-expire {
  font-size: 12px;
  color: #999;
}

.link-display {
  animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
</style>
