<!--
  页面名称：Recorder.vue
  页面描述：用例录制页面，通过 Playwright 浏览器录制用户操作，自动生成测试步骤

  @author czj
  @date 2026-07-09
-->

<template>
  <div class="recorder-page">
    <!-- 录制配置区 -->
    <el-card class="recorder-config" shadow="never">
      <template #header>
        <div class="card-header">
          <span>用例录制器</span>
          <el-tag :type="isRecording ? 'danger' : 'info'" effect="dark">
            {{ isRecording ? '录制中' : '未录制' }}
          </el-tag>
        </div>
      </template>

      <el-form :model="recorderForm" label-width="90px" inline>
        <el-form-item label="目标网站" required>
          <el-input
            v-model="recorderForm.url"
            placeholder="https://example.com"
            style="width: 300px"
            :disabled="isRecording"
          />
        </el-form-item>
        <el-form-item label="所属模块" required>
          <el-tree-select
            v-model="recorderForm.moduleId"
            :data="moduleTreeData"
            :props="{ label: 'name', value: 'id', children: 'children' }"
            placeholder="选择模块"
            check-strictly
            style="width: 200px"
            :disabled="isRecording"
          />
        </el-form-item>
        <el-form-item label="用例名称" required>
          <el-input
            v-model="recorderForm.caseName"
            placeholder="输入用例名称"
            style="width: 250px"
            :disabled="isRecording"
          />
        </el-form-item>
        <el-form-item label="浏览器模式">
          <el-switch
            v-model="recorderForm.headless"
            inactive-text="有头"
            active-text="无头"
            :disabled="isRecording"
          />
        </el-form-item>
        <el-form-item>
          <el-button
            v-if="!isRecording"
            type="primary"
            @click="startRecording"
            :loading="starting"
          >
            <el-icon><VideoCamera /></el-icon>
            开始录制
          </el-button>
          <el-button
            v-else
            type="danger"
            @click="stopRecording"
          >
            <el-icon><VideoPause /></el-icon>
            停止录制
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 录制内容区 -->
    <el-row :gutter="20" class="recorder-content">
      <!-- 步骤列表 -->
      <el-col :span="16">
        <el-card class="steps-panel" shadow="never">
          <template #header>
            <div class="card-header">
              <span>已捕获步骤 ({{ recordedSteps.length }})</span>
              <div class="step-actions">
                <el-button
                  size="small"
                  :icon="isPaused ? 'VideoPlay' : 'VideoPause'"
                  @click="togglePause"
                  v-if="isRecording"
                >
                  {{ isPaused ? '继续' : '暂停' }}
                </el-button>
                <el-button
                  size="small"
                  type="danger"
                  @click="clearSteps"
                  :disabled="recordedSteps.length === 0"
                >
                  清空步骤
                </el-button>
              </div>
            </div>
          </template>

          <div v-if="recordedSteps.length === 0" class="empty-hint">
            <el-empty description="点击「开始录制」按钮，在浏览器中操作目标网站，步骤将自动捕获" />
          </div>

          <div v-else class="step-list">
            <div
              v-for="(step, index) in recordedSteps"
              :key="index"
              class="step-item"
              :class="{ 'step-selected': selectedStepIndex === index }"
              @click="selectedStepIndex = index"
            >
              <div class="step-order">{{ step.order }}</div>
              <div class="step-info">
                <div class="step-name">{{ step.name }}</div>
                <div class="step-detail">
                  <el-tag size="small" type="info">{{ step.action }}</el-tag>
                  <span class="step-selector">{{ step.selector }}</span>
                  <span v-if="step.value" class="step-value">"{{ step.value }}"</span>
                </div>
              </div>
              <div class="step-ops">
                <el-button
                  size="small"
                  circle
                  :icon="'Top'"
                  :disabled="index === 0"
                  @click.stop="moveStep(index, 'up')"
                />
                <el-button
                  size="small"
                  circle
                  :icon="'Bottom'"
                  :disabled="index === recordedSteps.length - 1"
                  @click.stop="moveStep(index, 'down')"
                />
                <el-button
                  size="small"
                  circle
                  type="danger"
                  :icon="'Delete'"
                  @click.stop="removeStep(index)"
                />
              </div>
            </div>
          </div>
        </el-card>
      </el-col>

      <!-- 添加断言和步骤编辑 -->
      <el-col :span="8">
        <!-- 添加断言 -->
        <el-card class="assertion-panel" shadow="never">
          <template #header>
            <span>添加断言</span>
          </template>
          <el-form label-width="70px" size="small">
            <el-form-item label="选择器">
              <el-input v-model="assertion.selector" placeholder="#element" />
            </el-form-item>
            <el-form-item label="断言类型">
              <el-select v-model="assertion.type" style="width: 100%">
                <el-option label="元素可见" value="visible" />
                <el-option label="元素隐藏" value="hidden" />
                <el-option label="文本匹配" value="text" />
                <el-option label="文本包含" value="contains" />
                <el-option label="输入值匹配" value="value" />
                <el-option label="URL匹配" value="url" />
                <el-option label="标题匹配" value="title" />
              </el-select>
            </el-form-item>
            <el-form-item label="期望值">
              <el-input v-model="assertion.expected" placeholder="期望的值" />
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="addAssertion" :disabled="!assertion.selector">
                <el-icon><Plus /></el-icon>
                添加断言
              </el-button>
            </el-form-item>
          </el-form>
        </el-card>

        <!-- 步骤编辑 -->
        <el-card
          v-if="selectedStepIndex !== null && recordedSteps[selectedStepIndex]"
          class="edit-panel"
          shadow="never"
          style="margin-top: 20px"
        >
          <template #header>
            <span>编辑步骤</span>
          </template>
          <el-form label-width="70px" size="small">
            <el-form-item label="步骤名称">
              <el-input v-model="recordedSteps[selectedStepIndex].name" />
            </el-form-item>
            <el-form-item label="动作类型">
              <el-select v-model="recordedSteps[selectedStepIndex].action" style="width: 100%">
                <el-option label="导航" value="navigate" />
                <el-option label="点击" value="click" />
                <el-option label="输入" value="fill" />
                <el-option label="选择" value="select" />
                <el-option label="悬停" value="hover" />
                <el-option label="等待" value="wait" />
                <el-option label="断言" value="assert" />
                <el-option label="截图" value="screenshot" />
              </el-select>
            </el-form-item>
            <el-form-item label="选择器">
              <el-input v-model="recordedSteps[selectedStepIndex].selector" />
            </el-form-item>
            <el-form-item label="值">
              <el-input v-model="recordedSteps[selectedStepIndex].value" />
            </el-form-item>
            <el-form-item label="预期结果">
              <el-input v-model="recordedSteps[selectedStepIndex].expected" placeholder="预期结果描述" />
            </el-form-item>
          </el-form>
        </el-card>
      </el-col>
    </el-row>

    <!-- 底部保存 -->
    <div class="recorder-footer" v-if="recordedSteps.length > 0">
      <el-button type="success" size="large" @click="saveCase" :loading="saving">
        <el-icon><FolderChecked /></el-icon>
        保存为测试用例
      </el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
/**
 * 用例录制器页面
 *
 * 功能：
 * 1. 配置录制参数（目标URL、模块、用例名）
 * 2. 启动浏览器录制会话
 * 3. 实时捕获用户操作并展示步骤
 * 4. 手动添加断言验证
 * 5. 编辑、删除、排序步骤
 * 6. 保存为测试用例
 */

import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
  VideoCamera,
  VideoPause,
  Plus,
  FolderChecked,
} from '@element-plus/icons-vue'
import type { Module } from '@/types/module'
import { moduleApi, buildModuleTree } from '@/api/modules'
import { recorderApi } from '@/api/recorder'
import { systemApi } from '@/api/system'

const router = useRouter()

// ==================== 响应式数据 ====================

/** 录制表单 */
const recorderForm = ref({
  url: 'https://192.168.5.44',
  moduleId: '',
  caseName: '',
  headless: false,
})

/** 模块列表 */
const modules = ref<Module[]>([])

/** 模块树形数据（用于 el-tree-select） */
const moduleTreeData = computed(() => buildModuleTree(modules.value))

/** 是否录制中 */
const isRecording = ref(false)

/** 是否暂停 */
const isPaused = ref(false)

/** 开始录制加载中 */
const starting = ref(false)

/** 保存加载中 */
const saving = ref(false)

/** 录制会话 ID */
const sessionId = ref('')

/** 录制的步骤 */
const recordedSteps = ref<any[]>([])

/** 步骤计数器 */
let stepCounter = 0

/** 选中步骤索引 */
const selectedStepIndex = ref<number | null>(null)

/** 断言表单 */
const assertion = ref({
  selector: '',
  type: 'visible',
  expected: '',
})

// ==================== 方法定义 ====================

/**
 * 加载模块列表
 * 只加载 UI 测试模块，过滤掉安全测试模块
 */
const loadModules = async () => {
  try {
    const response = await moduleApi.list(0, 100, undefined, 'ui')
    modules.value = response.items
    if (modules.value.length > 0) {
      recorderForm.value.moduleId = modules.value[0].id
    }
  } catch (error: any) {
    ElMessage.error('加载模块列表失败: ' + error.message)
  }
}

/**
 * 开始录制
 */
const startRecording = async () => {
  // 参数校验
  if (!recorderForm.value.url.trim()) {
    ElMessage.warning('请输入目标网站URL')
    return
  }
  if (!recorderForm.value.moduleId) {
    ElMessage.warning('请选择所属模块')
    return
  }
  if (!recorderForm.value.caseName.trim()) {
    ElMessage.warning('请输入用例名称')
    return
  }

  starting.value = true

  try {
    const response = await recorderApi.start({
      url: recorderForm.value.url,
      module_id: recorderForm.value.moduleId,
      headless: recorderForm.value.headless,
    })

    sessionId.value = response.session_id
    isRecording.value = true
    isPaused.value = false
    stepCounter = 0
    recordedSteps.value = []

    ElMessage.success('录制会话已启动，请在浏览器中操作目标网站')

  } catch (error: any) {
    ElMessage.error('启动录制失败: ' + error.message)
  } finally {
    starting.value = false
  }
}

/**
 * 停止录制
 */
const stopRecording = async () => {
  try {
    if (sessionId.value) {
      await recorderApi.stop(sessionId.value)
    }
    isRecording.value = false
    isPaused.value = false
    ElMessage.success(`录制完成，共捕获 ${recordedSteps.value.length} 个步骤`)
  } catch (error: any) {
    ElMessage.error('停止录制失败: ' + error.message)
  }
}

/**
 * 暂停/继续录制
 */
const togglePause = () => {
  isPaused.value = !isPaused.value
  ElMessage.info(isPaused.value ? '录制已暂停' : '录制已继续')
}

/**
 * 清空步骤
 */
const clearSteps = () => {
  recordedSteps.value = []
  stepCounter = 0
  selectedStepIndex.value = null
  ElMessage.success('步骤已清空')
}

/**
 * 添加断言步骤
 */
const addAssertion = () => {
  if (!assertion.value.selector) {
    ElMessage.warning('请输入选择器')
    return
  }

  stepCounter++
  const step = {
    order: stepCounter,
    name: `断言验证: ${assertion.value.selector}`,
    action: 'assert',
    selector: assertion.value.selector,
    params: {
      selector: assertion.value.selector,
      type: assertion.value.type,
      expected: assertion.value.expected,
    },
    expected: assertion.value.expected || '验证通过',
    value: '',
  }

  recordedSteps.value.push(step)

  // 同步到后端
  if (sessionId.value) {
    recorderApi.addAssertion(sessionId.value, {
      selector: assertion.value.selector,
      type: assertion.value.type,
      expected: assertion.value.expected,
    }).catch(err => console.error('同步断言失败:', err))
  }

  // 清空断言表单
  assertion.value = { selector: '', type: 'visible', expected: '' }
  ElMessage.success('断言步骤已添加')
}

/**
 * 移动步骤
 */
const moveStep = (index: number, direction: 'up' | 'down') => {
  const targetIndex = direction === 'up' ? index - 1 : index + 1
  const steps = recordedSteps.value
  const temp = steps[index]
  steps[index] = steps[targetIndex]
  steps[targetIndex] = temp

  // 重新编号
  steps.forEach((step, i) => {
    step.order = i + 1
  })

  if (selectedStepIndex.value === index) {
    selectedStepIndex.value = targetIndex
  }
}

/**
 * 删除步骤
 */
const removeStep = (index: number) => {
  recordedSteps.value.splice(index, 1)
  // 重新编号
  recordedSteps.value.forEach((step, i) => {
    step.order = i + 1
  })
  stepCounter = recordedSteps.value.length

  if (selectedStepIndex.value === index) {
    selectedStepIndex.value = null
  }
  ElMessage.success('步骤已删除')
}

/**
 * 保存为测试用例
 */
const saveCase = async () => {
  if (!recordedSteps.value.length) {
    ElMessage.warning('没有可保存的步骤')
    return
  }

  if (!sessionId.value) {
    ElMessage.warning('录制会话已失效')
    return
  }

  saving.value = true

  try {
    const result = await recorderApi.save({
      session_id: sessionId.value,
      name: recorderForm.value.caseName,
      module_id: recorderForm.value.moduleId,
      priority: 'medium',
    })

    ElMessage.success(result.message)
    router.push('/cases')

  } catch (error: any) {
    ElMessage.error('保存失败: ' + error.message)
  } finally {
    saving.value = false
  }
}

// ==================== 生命周期 ====================
onMounted(async () => {
  // 加载系统配置中的被测系统 URL，覆盖默认值
  try {
    const config = await systemApi.getDingTalkConfig()
    if (config.target_url) {
      recorderForm.value.url = config.target_url
    }
  } catch {
    /* 静默失败，使用默认值 */
  }
  loadModules()
})
</script>

<style lang="scss" scoped>
.recorder-page {
  padding-bottom: 60px;

  .recorder-config {
    margin-bottom: 20px;

    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
  }

  .recorder-content {
    .steps-panel {
      .card-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }

      .empty-hint {
        padding: 40px;
      }

      .step-list {
        .step-item {
          display: flex;
          align-items: center;
          gap: 12px;
          padding: 12px;
          border: 1px solid #ebeef5;
          border-radius: 4px;
          margin-bottom: 8px;
          cursor: pointer;
          transition: all 0.3s;

          &:hover {
            border-color: #409eff;
            background: #ecf5ff;
          }

          &.step-selected {
            border-color: #409eff;
            background: #ecf5ff;
            box-shadow: 0 0 0 1px #409eff;
          }

          .step-order {
            width: 28px;
            height: 28px;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #409eff;
            color: #fff;
            border-radius: 50%;
            font-size: 13px;
            font-weight: 600;
            flex-shrink: 0;
          }

          .step-info {
            flex: 1;

            .step-name {
              font-weight: 600;
              margin-bottom: 4px;
            }

            .step-detail {
              display: flex;
              align-items: center;
              gap: 8px;
              font-size: 13px;
              color: #909399;

              .step-selector {
                color: #409eff;
                font-family: monospace;
              }

              .step-value {
                color: #67c23a;
              }
            }
          }

          .step-ops {
            display: flex;
            gap: 4px;
          }
        }
      }
    }
  }

  .recorder-footer {
    position: fixed;
    bottom: 0;
    left: 220px;
    right: 0;
    padding: 16px 20px;
    background: #fff;
    border-top: 1px solid #ebeef5;
    text-align: center;
    z-index: 100;
  }
}
</style>