<!--
  组件名称：CaseStepEditor.vue
  组件描述：UI 自动化测试步骤编辑器，支持增删/上下移/动态参数表单

  功能：
  1. 添加 / 删除步骤
  2. 上移 / 下移（自动重排 order）
  3. 按动作类型动态显示参数输入框（与执行器 12 种动作 + 8 种断言一致）
  4. v-model 双向绑定 steps 数组

  说明：
  - 参数统一存储在 step.params 中（snake_case 键），与后端 playwright_executor 读取方式一致
  - executor 读取：params.selector / params.url / params.value / params.type / params.expected / params.timeout / params.key / params.direction / params.distance / params.force / params.full_page

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

<template>
  <div class="case-step-editor">
    <!-- 步骤为空时的占位 -->
    <el-empty
      v-if="!steps.length"
      description="暂无步骤，点击下方按钮添加"
      :image-size="60"
    />

    <!-- 步骤列表 -->
    <div
      v-for="(step, index) in steps"
      :key="index"
      class="step-item"
    >
      <!-- 步骤头部：序号 + 名称 + 动作 + 操作按钮 -->
      <div class="step-header">
        <span class="step-order">{{ step.order }}</span>
        <el-input
          v-model="step.name"
          placeholder="步骤名称"
          size="small"
          style="width: 200px"
        />
        <el-select
          v-model="step.action"
          size="small"
          style="width: 200px"
          @change="onActionChange(step)"
        >
          <el-option
            v-for="opt in ACTION_OPTIONS"
            :key="opt.value"
            :label="opt.label"
            :value="opt.value"
          />
        </el-select>

        <div class="step-ops">
          <el-button
            size="small"
            link
            :disabled="index === 0"
            @click="moveStep(index, -1)"
            title="上移"
          >
            <el-icon><Top /></el-icon>
          </el-button>
          <el-button
            size="small"
            link
            :disabled="index === steps.length - 1"
            @click="moveStep(index, 1)"
            title="下移"
          >
            <el-icon><Bottom /></el-icon>
          </el-button>
          <el-button
            size="small"
            type="danger"
            link
            @click="removeStep(index)"
            title="删除"
          >
            <el-icon><Delete /></el-icon>
          </el-button>
        </div>
      </div>

      <!-- 动态参数表单 -->
      <div class="step-params">
        <!-- 导航 -->
        <template v-if="step.action === 'navigate'">
          <el-input
            v-model="getParams(step).url"
            placeholder="导航 URL，如 https://192.168.5.44 或 /login"
            size="small"
          >
            <template #prepend>URL</template>
          </el-input>
        </template>

        <!-- 滚动 -->
        <template v-else-if="step.action === 'scroll'">
          <el-select v-model="getParams(step).direction" size="small" style="width: 120px">
            <el-option label="向下" value="down" />
            <el-option label="向上" value="up" />
          </el-select>
          <el-input-number
            v-model="getParams(step).distance"
            :min="0"
            :step="100"
            size="small"
            controls-position="right"
            style="width: 140px"
          />
          <span class="param-hint">滚动距离(px)</span>
        </template>

        <!-- 按键 -->
        <template v-else-if="step.action === 'press'">
          <el-input
            v-model="getParams(step).key"
            placeholder="按键，如 Enter / Tab / Escape / ArrowDown"
            size="small"
            style="width: 280px"
          >
            <template #prepend>按键</template>
          </el-input>
          <el-input
            v-model="getParams(step).selector"
            placeholder="选择器（可选，不填则在当前焦点按键）"
            size="small"
            style="width: 320px"
          />
        </template>

        <!-- 断言 -->
        <template v-else-if="step.action === 'assert'">
          <el-select
            v-model="getParams(step).type"
            size="small"
            style="width: 160px"
            placeholder="断言类型"
          >
            <el-option
              v-for="t in ASSERT_TYPE_OPTIONS"
              :key="t.value"
              :label="t.label"
              :value="t.value"
            />
          </el-select>
          <el-input
            v-if="needsSelector(getParams(step).type)"
            v-model="getParams(step).selector"
            placeholder="元素选择器"
            size="small"
            style="width: 280px"
          />
          <el-input
            v-if="needsExpected(getParams(step).type)"
            v-model="getParams(step).expected"
            placeholder="期望值（文本/数值/URL片段）"
            size="small"
            style="width: 280px"
          />
        </template>

        <!-- 截图 -->
        <template v-else-if="step.action === 'screenshot'">
          <el-switch
            v-model="getParams(step).full_page"
            active-text="整页截图"
            inactive-text="可视区域"
          />
        </template>

        <!-- 需要选择器的动作：click / fill / select / hover / wait / check / uncheck -->
        <template v-else>
          <el-input
            v-model="getParams(step).selector"
            placeholder="元素选择器（支持 CSS / XPath / 文本）"
            size="small"
            style="width: 360px"
          >
            <template #prepend>选择器</template>
          </el-input>
          <!-- fill / select 需要值 -->
          <el-input
            v-if="step.action === 'fill' || step.action === 'select'"
            v-model="getParams(step).value"
            placeholder="输入值"
            size="small"
            style="width: 220px"
          >
            <template #prepend>值</template>
          </el-input>
          <!-- wait 需要超时 -->
          <el-input-number
            v-if="step.action === 'wait'"
            v-model="getParams(step).timeout"
            :min="0"
            :step="1000"
            size="small"
            controls-position="right"
            style="width: 160px"
          />
          <span v-if="step.action === 'wait'" class="param-hint">超时(ms)</span>
          <!-- click / fill / check / uncheck 支持强制 -->
          <el-checkbox
            v-if="['click', 'fill', 'check', 'uncheck'].includes(step.action)"
            v-model="getParams(step).force"
          >
            强制操作
          </el-checkbox>
        </template>
      </div>
    </div>

    <!-- 添加步骤按钮 -->
    <el-button
      type="primary"
      plain
      size="small"
      class="add-step-btn"
      @click="addStep"
    >
      <el-icon><Plus /></el-icon>
      添加步骤
    </el-button>
  </div>
</template>

<script setup lang="ts">
/**
 * CaseStepEditor 组件
 *
 * UI 自动化测试步骤编辑器。
 * 通过 v-model 双向绑定 steps 数组，参数存储在 step.params 中。
 */
import { computed } from 'vue'
import { Top, Bottom, Delete, Plus } from '@element-plus/icons-vue'

/** 步骤对象结构 */
interface Step {
  order: number
  name: string
  action: string
  params: Record<string, any>
  [key: string]: any
}

const props = defineProps<{
  /** v-model 绑定的步骤数组 */
  modelValue: Step[]
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', value: Step[]): void
}>()

/** steps 计算属性：直接操作同一数组引用，变更后 emit 通知父组件 */
const steps = computed(() => props.modelValue)

// ==================== 动作与断言配置 ====================

/** 执行器支持的 12 种动作（与 playwright_executor 一致） */
const ACTION_OPTIONS = [
  { label: '导航 (navigate)', value: 'navigate' },
  { label: '点击 (click)', value: 'click' },
  { label: '填充 (fill)', value: 'fill' },
  { label: '下拉选择 (select)', value: 'select' },
  { label: '悬停 (hover)', value: 'hover' },
  { label: '等待元素 (wait)', value: 'wait' },
  { label: '滚动 (scroll)', value: 'scroll' },
  { label: '按键 (press)', value: 'press' },
  { label: '勾选 (check)', value: 'check' },
  { label: '取消勾选 (uncheck)', value: 'uncheck' },
  { label: '断言 (assert)', value: 'assert' },
  { label: '截图 (screenshot)', value: 'screenshot' },
]

/** 断言类型选项（8 种） */
const ASSERT_TYPE_OPTIONS = [
  { label: '元素可见 (visible)', value: 'visible' },
  { label: '元素隐藏 (hidden)', value: 'hidden' },
  { label: '文本相等 (text)', value: 'text' },
  { label: '文本包含 (contains)', value: 'contains' },
  { label: '输入值相等 (value)', value: 'value' },
  { label: '元素数量 (count)', value: 'count' },
  { label: 'URL 包含 (url)', value: 'url' },
  { label: '标题包含 (title)', value: 'title' },
]

// ==================== 辅助方法 ====================

/**
 * 获取步骤的 params 对象（不存在则初始化为空对象）
 * 确保嵌套对象可响应式绑定
 */
const getParams = (step: Step): Record<string, any> => {
  if (!step.params || typeof step.params !== 'object') {
    step.params = {}
  }
  return step.params
}

/** 断言是否需要选择器 */
const needsSelector = (type: string): boolean =>
  ['visible', 'hidden', 'text', 'contains', 'value', 'count'].includes(type || '')

/** 断言是否需要期望值 */
const needsExpected = (type: string): boolean =>
  ['text', 'contains', 'value', 'count', 'url', 'title'].includes(type || '')

/**
 * 动作变更时重置无关参数，避免残留干扰
 * 保留通用字段，清空动作特有字段
 */
const onActionChange = (step: Step) => {
  const p = getParams(step)
  // 断言默认类型为 visible
  if (step.action === 'assert' && !p.type) {
    p.type = 'visible'
  }
  // 滚动默认向下 300px
  if (step.action === 'scroll') {
    if (!p.direction) p.direction = 'down'
    if (p.distance == null) p.distance = 300
  }
  emitChange()
}

// ==================== 步骤增删移动 ====================

/** 添加步骤（自动编号 + 默认动作 click） */
const addStep = () => {
  steps.value.push({
    order: steps.value.length + 1,
    name: `步骤${steps.value.length + 1}`,
    action: 'click',
    params: { selector: '' },
  })
  emitChange()
}

/** 删除步骤（重排 order） */
const removeStep = (index: number) => {
  steps.value.splice(index, 1)
  renumber()
}

/**
 * 移动步骤
 * @param index 当前索引
 * @param dir -1 上移 / 1 下移
 */
const moveStep = (index: number, dir: number) => {
  const target = index + dir
  if (target < 0 || target >= steps.value.length) return
  const tmp = steps.value[index]
  steps.value[index] = steps.value[target]
  steps.value[target] = tmp
  renumber()
}

/** 重新编号 order（从 1 开始） */
const renumber = () => {
  steps.value.forEach((s, i) => {
    s.order = i + 1
  })
  emitChange()
}

/** 通知父组件变更（保持 v-model 同步） */
const emitChange = () => {
  emit('update:modelValue', steps.value)
}
</script>

<style lang="scss" scoped>
.case-step-editor {
  width: 100%;

  .step-item {
    padding: 12px;
    margin-bottom: 12px;
    background: #fafbfc;
    border: 1px solid #ebeef5;
    border-radius: 6px;

    .step-header {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-bottom: 10px;

      .step-order {
        flex-shrink: 0;
        width: 24px;
        height: 24px;
        line-height: 24px;
        text-align: center;
        background: #409eff;
        color: #fff;
        border-radius: 50%;
        font-size: 12px;
        font-weight: 600;
      }

      .step-ops {
        margin-left: auto;
        display: flex;
        align-items: center;
      }
    }

    .step-params {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 8px;
      padding-left: 32px;

      .param-hint {
        font-size: 12px;
        color: #909399;
      }
    }
  }

  .add-step-btn {
    width: 100%;
    border-style: dashed;
  }
}
</style>
