<script setup lang="ts">
import type {
  SkillCapabilityBinding,
  SkillCapabilityCandidate,
  SkillCapabilityType,
} from './types';

import {
  computed,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  toRaw,
  watch,
} from 'vue';

import { Delete, Edit, MoreFilled, Plus } from '@element-plus/icons-vue';
import { EasyFlowFormModal } from '@easyflow/common-ui';
import {
  ElButton,
  ElCollapse,
  ElCollapseItem,
  ElDropdown,
  ElDropdownItem,
  ElDropdownMenu,
  ElEmpty,
  ElForm,
  ElFormItem,
  ElInput,
  ElInputNumber,
  ElMessage,
  ElMessageBox,
  ElOption,
  ElSegmented,
  ElSelect,
  ElSwitch,
  ElTag,
} from 'element-plus';

import {
  getSkillCapabilityBindings,
  getSkillCapabilityCandidates,
  getSkillCapabilityTools,
  getSkillDetail,
  replaceSkillCapabilityBindings,
} from './api';
import {
  isSkillCapabilityConflictError,
  resolveSkillApiErrorMessage,
} from './skill-api-error';
import {
  sanitizeCapabilityOptions,
  sanitizeHitlConfig,
  shouldLoadMcpTools,
} from './skill-capability';

const props = withDefaults(
  defineProps<{
    capabilityHash?: string;
    embedded?: boolean;
    locked?: boolean;
    readonly?: boolean;
    skillId: number | string;
  }>(),
  { capabilityHash: '', embedded: false, locked: false, readonly: false },
);

const emit = defineEmits<{
  dirty: [dirty: boolean];
  saved: [bindings: SkillCapabilityBinding[]];
}>();

const bindings = ref<SkillCapabilityBinding[]>([]);
const candidates = ref<SkillCapabilityCandidate[]>([]);
const loading = ref(false);
const saveState = ref<'conflict' | 'error' | 'saved' | 'saving'>('saved');
const saveError = ref('');
const dialogSaving = ref(false);
const candidateLoading = ref(false);
const toolsLoading = ref(false);
const loadError = ref('');
const candidateError = ref('');
const mcpTools = ref<string[]>([]);
const toolsError = ref('');
const dialogOpen = ref(false);
const editingIndex = ref(-1);
const advancedSections = ref<string[]>([]);
const form = reactive<SkillCapabilityBinding>({
  capabilityType: 'WORKFLOW',
  enabled: true,
  executionMode: 'SYNC',
  hitlEnabled: false,
  runtimeName: '',
  selectedToolNamesJson: [],
  selectionMode: 'ALL',
});

const selectedCandidate = computed(() =>
  candidates.value.find(
    (candidate) => String(candidate.targetId) === String(form.targetId),
  ),
);
const baseline = ref('[]');
const baselineCapabilityHash = ref('');
const dirty = computed(() => JSON.stringify(bindings.value) !== baseline.value);
const saving = computed(() => saveState.value === 'saving');
const saveStateLabel = computed(() => {
  if (saveState.value === 'saving') return '保存中';
  if (saveState.value === 'error') return '保存失败 · 重试';
  if (saveState.value === 'conflict') return '版本冲突';
  return '已保存';
});
const capabilityTypeOptions = [
  { label: '工作流', value: 'WORKFLOW' },
  { label: '插件工具', value: 'PLUGIN_ITEM' },
  { label: 'MCP', value: 'MCP' },
];
const runtimeNameError = computed(() => {
  const value = String(form.runtimeName || '').trim();
  if (!value) return '请输入运行时名称';
  if (!/^[a-z][\w-]{0,63}$/i.test(value)) {
    return '需以字母开头，仅使用字母、数字、下划线或连字符';
  }
  return '';
});
const hitlPrompt = createHitlStringModel('prompt');
const hitlTitle = createHitlStringModel('title');
const hitlConfirmLabel = createHitlStringModel('confirmLabel');
const hitlCancelLabel = createHitlStringModel('cancelLabel');
const timeoutMs = createOptionNumberModel('timeoutMs');
const retryCount = createOptionNumberModel('retryCount');
const readOnlyOption = computed({
  get: () => form.optionsJson?.readOnly === true,
  set: (value: boolean) => updateOption('readOnly', value),
});
const timeoutError = computed(() => {
  if (timeoutMs.value === undefined) return '';
  if (
    !Number.isInteger(timeoutMs.value) ||
    timeoutMs.value < 100 ||
    timeoutMs.value > 300_000
  ) {
    return '超时时间需为 100 到 300000 毫秒的整数';
  }
  return '';
});
const retryError = computed(() => {
  if (retryCount.value === undefined) return '';
  if (
    !Number.isInteger(retryCount.value) ||
    retryCount.value < 0 ||
    retryCount.value > 10
  ) {
    return '重试次数需为 0 到 10 的整数';
  }
  return '';
});
let searchTimer: ReturnType<typeof setTimeout> | undefined;
let candidateRequest = 0;
let bindingRequest = 0;
let toolRequest = 0;
let rowKeySequence = 0;
let saveLoopPromise: Promise<boolean> | undefined;
let saveEpoch = 0;
let draggedIndex = -1;
const rowKeys = new WeakMap<object, string>();

onMounted(() => loadBindings());
watch(
  () => props.skillId,
  () => loadBindings(),
);
watch(
  () => props.capabilityHash,
  (value) => {
    if (!dirty.value && !loading.value && !loadError.value) {
      baselineCapabilityHash.value = value || '';
    }
  },
);
watch(dirty, (value) => emit('dirty', value), { immediate: true });
onBeforeUnmount(() => {
  clearTimeout(searchTimer);
  saveEpoch++;
  bindingRequest++;
  candidateRequest++;
  toolRequest++;
});

async function loadBindings(options: { refreshHash?: boolean } = {}) {
  saveEpoch++;
  const request = ++bindingRequest;
  clearTimeout(searchTimer);
  searchTimer = undefined;
  candidateRequest++;
  candidateLoading.value = false;
  dialogOpen.value = false;
  editingIndex.value = -1;
  resetForm();
  bindings.value = [];
  baseline.value = '[]';
  baselineCapabilityHash.value = '';
  loadError.value = '';
  saveError.value = '';
  saveState.value = 'saved';
  if (!props.skillId || props.skillId === 'new') {
    loading.value = false;
    return;
  }
  loading.value = true;
  try {
    let capabilityHash = props.capabilityHash || '';
    if (options.refreshHash) {
      const detailRes = await getSkillDetail(props.skillId);
      if (request !== bindingRequest) return;
      if (detailRes.errorCode !== 0) {
        loadError.value = detailRes.message || '能力配置版本加载失败，请重试';
        return;
      }
      capabilityHash = detailRes.data.capabilityHash || '';
    }
    const res = await getSkillCapabilityBindings(props.skillId);
    if (request === bindingRequest) {
      if (res.errorCode === 0) {
        bindings.value = res.data || [];
        baseline.value = JSON.stringify(bindings.value);
        baselineCapabilityHash.value = options.refreshHash
          ? capabilityHash
          : props.capabilityHash || '';
        emit('dirty', false);
      } else {
        loadError.value = res.message || '能力绑定加载失败，请重试';
      }
    }
  } catch (error) {
    if (request === bindingRequest)
      loadError.value = resolveSkillApiErrorMessage(
        error,
        '能力绑定加载失败，请重试',
      );
  } finally {
    if (request === bindingRequest) loading.value = false;
  }
}

function resetForm(type: SkillCapabilityType = 'WORKFLOW') {
  toolRequest++;
  toolsLoading.value = false;
  Object.assign(form, {
    bindingKey: undefined,
    capabilityType: type,
    enabled: true,
    executionMode: type === 'MCP' ? undefined : 'SYNC',
    hitlConfigJson: {},
    hitlEnabled: false,
    id: undefined,
    optionsJson: {},
    runtimeName: '',
    selectedToolNamesJson: [],
    selectionMode: 'ALL',
    sortNo: undefined,
    targetId: undefined,
    targetLogicalRef: undefined,
    targetName: undefined,
    targetStatus: undefined,
    targetSummary: undefined,
    resolvedToolNames: undefined,
  });
  candidates.value = [];
  mcpTools.value = [];
  toolsError.value = '';
  advancedSections.value = [];
  candidateError.value = '';
}

async function openCreate() {
  if (
    props.readonly ||
    props.locked ||
    dialogSaving.value ||
    saving.value ||
    loadError.value
  )
    return;
  if (bindings.value.length >= 200) {
    ElMessage.warning('单个 Skill 最多绑定 200 项能力');
    return;
  }
  editingIndex.value = -1;
  resetForm();
  dialogOpen.value = true;
  await fetchCandidates('');
}

async function openEdit(binding: SkillCapabilityBinding, index: number) {
  if (props.readonly || props.locked || dialogSaving.value) return;
  editingIndex.value = index;
  resetForm(binding.capabilityType);
  let executionMode: SkillCapabilityBinding['executionMode'];
  if (binding.capabilityType !== 'MCP') {
    executionMode = binding.executionMode === 'ASYNC' ? 'ASYNC' : 'SYNC';
  }
  Object.assign(form, structuredClone(toRaw(binding)), {
    executionMode,
    hitlConfigJson: sanitizeHitlConfig(binding.hitlConfigJson),
    optionsJson: sanitizeCapabilityOptions(binding.optionsJson),
    selectedToolNamesJson: normalizeSelectedToolNames(
      binding.selectedToolNamesJson,
    ),
  });
  if (
    Object.keys(form.hitlConfigJson || {}).length > 0 ||
    Object.keys(form.optionsJson || {}).length > 0
  ) {
    advancedSections.value = ['advanced'];
  }
  dialogOpen.value = true;
  await fetchCandidates(binding.targetName || '');
  if (
    shouldLoadMcpTools(
      binding.capabilityType,
      binding.selectionMode,
      binding.targetId,
    )
  ) {
    await loadMcpTools(binding.targetId);
  }
}

function searchCandidates(keyword: string) {
  clearTimeout(searchTimer);
  searchTimer = setTimeout(() => fetchCandidates(keyword), 240);
}

async function fetchCandidates(keyword: string) {
  const request = ++candidateRequest;
  candidateLoading.value = true;
  candidateError.value = '';
  try {
    const res = await getSkillCapabilityCandidates(
      form.capabilityType,
      keyword,
    );
    if (request === candidateRequest) {
      if (res.errorCode === 0) {
        candidates.value = res.data || [];
      } else {
        candidates.value = [];
        candidateError.value = res.message || '候选资源加载失败，请重试';
      }
    }
  } catch (error) {
    if (request === candidateRequest) {
      candidates.value = [];
      candidateError.value = resolveSkillApiErrorMessage(
        error,
        '候选资源加载失败，请重试',
      );
    }
  } finally {
    if (request === candidateRequest) candidateLoading.value = false;
  }
}

async function changeCapabilityType(type: SkillCapabilityType) {
  resetForm(type);
  await fetchCandidates('');
}

async function changeCandidate(targetId: number | string | undefined) {
  form.targetId = targetId;
  const candidate = selectedCandidate.value;
  if (!candidate) return;
  toolRequest++;
  toolsLoading.value = false;
  toolsError.value = '';
  mcpTools.value = [];
  form.targetName = candidate.name;
  form.targetLogicalRef = candidate.logicalRef;
  form.targetStatus =
    candidate.status === 'AVAILABLE' ? 'AVAILABLE' : 'UNAVAILABLE';
  form.runtimeName = resolveDefaultRuntimeName(
    form.capabilityType,
    candidate.name,
    candidate.logicalRef,
    candidate.targetId,
  );
  if (
    shouldLoadMcpTools(form.capabilityType, form.selectionMode, form.targetId)
  ) {
    form.selectedToolNamesJson = [];
    await loadMcpTools(candidate.targetId);
  }
}

async function changeSelectionMode(mode: 'ALL' | 'SELECTED') {
  form.selectionMode = mode;
  toolRequest++;
  toolsLoading.value = false;
  toolsError.value = '';
  mcpTools.value = [];
  if (mode === 'ALL') {
    form.selectedToolNamesJson = [];
    return;
  }
  if (shouldLoadMcpTools(form.capabilityType, mode, form.targetId)) {
    await loadMcpTools(form.targetId);
  }
}

async function loadMcpTools(targetId: number | string) {
  const request = ++toolRequest;
  toolsLoading.value = true;
  toolsError.value = '';
  try {
    const res = await getSkillCapabilityTools(targetId);
    if (request === toolRequest) {
      if (res.errorCode === 0) {
        mcpTools.value = res.data.toolNames || [];
      } else {
        mcpTools.value = [];
        toolsError.value = res.message || 'MCP 工具加载失败，请重试';
      }
    }
  } catch (error) {
    if (request === toolRequest) {
      mcpTools.value = [];
      toolsError.value = resolveSkillApiErrorMessage(
        error,
        'MCP 工具加载失败，请重试',
      );
    }
  } finally {
    if (request === toolRequest) toolsLoading.value = false;
  }
}

function normalizeSelectedToolNames(value: unknown) {
  if (Array.isArray(value)) return value.map(String);
  return [];
}

function createHitlStringModel(key: string) {
  return computed({
    get: () => String(form.hitlConfigJson?.[key] || ''),
    set: (value: string) => updateHitlConfig(key, value),
  });
}

function createOptionNumberModel(key: 'retryCount' | 'timeoutMs') {
  return computed<number | undefined>({
    get: () => {
      const value = form.optionsJson?.[key];
      return typeof value === 'number' ? value : undefined;
    },
    set: (value) => updateOption(key, value),
  });
}

function updateHitlConfig(key: string, value: string) {
  const next = { ...form.hitlConfigJson };
  const normalized = value.trim();
  if (normalized) next[key] = value;
  else delete next[key];
  form.hitlConfigJson = next;
}

function updateOption(key: string, value: boolean | number | undefined) {
  const next = { ...form.optionsJson };
  if (value === undefined) delete next[key];
  else next[key] = value;
  form.optionsJson = next;
}

function handleHitlChange(value: boolean | number | string) {
  if (Boolean(value) && !advancedSections.value.includes('advanced')) {
    advancedSections.value.push('advanced');
  }
}

function toRuntimeName(value: string) {
  return value
    .trim()
    .toLowerCase()
    .replaceAll(/[^a-z0-9_-]+/g, '_')
    .replaceAll(/^_+|_+$/g, '')
    .slice(0, 64);
}

function resolveDefaultRuntimeName(
  capabilityType: SkillCapabilityType,
  name: string,
  logicalRef: string | undefined,
  targetId: number | string,
) {
  const readableName = toRuntimeName(name);
  if (readableName) return readableName;
  const logicalName = toRuntimeName(logicalRef || '');
  if (logicalName) return logicalName;
  const prefix =
    capabilityType === 'WORKFLOW'
      ? 'workflow'
      : capabilityType === 'MCP'
        ? 'mcp'
        : 'plugin';
  return toRuntimeName(`${prefix}_${targetId}`);
}

async function confirmBinding() {
  if (!form.targetId) {
    ElMessage.warning('请选择能力资源');
    return;
  }
  if (toolsError.value) {
    ElMessage.warning(toolsError.value);
    return;
  }
  if (runtimeNameError.value) {
    ElMessage.warning(runtimeNameError.value);
    return;
  }
  if (timeoutError.value || retryError.value) {
    ElMessage.warning(timeoutError.value || retryError.value);
    advancedSections.value = ['advanced'];
    return;
  }
  if (
    form.capabilityType === 'MCP' &&
    form.selectionMode === 'SELECTED' &&
    !form.selectedToolNamesJson?.length
  ) {
    ElMessage.warning('至少选择一个 MCP 工具');
    return;
  }
  const next: SkillCapabilityBinding = structuredClone(toRaw(form));
  const duplicate = bindings.value.some(
    (binding, index) =>
      index !== editingIndex.value &&
      binding.enabled !== false &&
      next.enabled !== false &&
      String(binding.runtimeName || '').toLowerCase() ===
        String(next.runtimeName || '').toLowerCase(),
  );
  if (duplicate) {
    ElMessage.warning('运行时名称不能与其他已启用能力重复');
    return;
  }
  if (editingIndex.value >= 0)
    bindings.value.splice(editingIndex.value, 1, next);
  else bindings.value.push(next);
  bindings.value.forEach((binding, index) => (binding.sortNo = index));
  dialogSaving.value = true;
  const saved = await queueAutoSave();
  dialogSaving.value = false;
  if (saved) dialogOpen.value = false;
}

async function move(index: number, offset: -1 | 1) {
  const target = index + offset;
  if (target < 0 || target >= bindings.value.length) return;
  const [binding] = bindings.value.splice(index, 1);
  if (binding) bindings.value.splice(target, 0, binding);
  bindings.value.forEach((item, itemIndex) => (item.sortNo = itemIndex));
  await queueAutoSave();
}

async function toggleBinding(index: number, value: boolean | string | number) {
  const binding = bindings.value[index];
  if (!binding) return;
  binding.enabled = Boolean(value);
  await queueAutoSave();
}

function startDrag(index: number) {
  draggedIndex = index;
}

async function dropAt(index: number) {
  if (draggedIndex < 0 || draggedIndex === index) return;
  const from = draggedIndex;
  draggedIndex = -1;
  const [binding] = bindings.value.splice(from, 1);
  if (binding) bindings.value.splice(index, 0, binding);
  bindings.value.forEach((item, itemIndex) => (item.sortNo = itemIndex));
  await queueAutoSave();
}

async function reloadBindings() {
  if (dirty.value) {
    try {
      await ElMessageBox.confirm(
        '重新加载会丢弃当前能力配置的本地修改。',
        '重新加载能力配置',
        {
          cancelButtonText: '继续编辑',
          confirmButtonText: '重新加载',
          type: 'warning',
        },
      );
    } catch {
      return;
    }
  }
  await loadBindings({ refreshHash: true });
}

async function reapplyLocalChanges() {
  if (saving.value || props.readonly || !dirty.value) return;
  saveState.value = 'saving';
  saveError.value = '';
  try {
    const detailRes = await getSkillDetail(props.skillId);
    if (detailRes.errorCode !== 0) {
      throw new Error(detailRes.message || '能力配置版本加载失败');
    }
    baselineCapabilityHash.value = detailRes.data.capabilityHash || '';
    saveState.value = 'saved';
    await queueAutoSave();
  } catch (error) {
    saveState.value = 'error';
    saveError.value = resolveSkillApiErrorMessage(
      error,
      '能力配置版本加载失败，请重试',
    );
  }
}

function retryAutoSave() {
  if (saving.value || props.readonly || !dirty.value) return;
  saveState.value = 'saved';
  saveError.value = '';
  void queueAutoSave();
}

function queueAutoSave() {
  if (saveLoopPromise) return saveLoopPromise;
  saveLoopPromise = runSaveLoop().finally(() => {
    saveLoopPromise = undefined;
  });
  return saveLoopPromise;
}

async function runSaveLoop() {
  if (props.readonly || loadError.value) return false;
  const epoch = saveEpoch;
  const targetSkillId = String(props.skillId);
  while (dirty.value && epoch === saveEpoch) {
    if (!/^[a-f0-9]{64}$/.test(baselineCapabilityHash.value)) {
      saveState.value = 'error';
      saveError.value = '能力配置版本缺失，请重新加载后再保存';
      return false;
    }
    const payload = structuredClone(toRaw(bindings.value));
    const payloadJson = JSON.stringify(payload);
    const persistedBefore = baseline.value;
    saveState.value = 'saving';
    saveError.value = '';
    try {
      const res = await replaceSkillCapabilityBindings(
        props.skillId,
        payload,
        baselineCapabilityHash.value,
      );
      if (epoch !== saveEpoch || String(props.skillId) !== targetSkillId) {
        return false;
      }
      if (res.errorCode !== 0) {
        throw new Error(res.message || '能力绑定保存失败');
      }
      const persistedBindings = res.data.bindings || [];
      baseline.value = JSON.stringify(persistedBindings);
      baselineCapabilityHash.value = res.data.capabilityHash;
      if (JSON.stringify(bindings.value) === payloadJson) {
        bindings.value = persistedBindings;
      }
      emit('saved', persistedBindings);
    } catch (error) {
      if (epoch !== saveEpoch) return false;
      if (isSkillCapabilityConflictError(error)) {
        saveState.value = 'conflict';
        saveError.value = '能力配置已在其他位置更新，本地修改已保留。';
      } else {
        if (JSON.stringify(bindings.value) === payloadJson) {
          bindings.value = JSON.parse(persistedBefore);
        }
        saveState.value = 'error';
        saveError.value = resolveSkillApiErrorMessage(
          error,
          '能力绑定保存失败，请重试',
        );
      }
      return false;
    }
  }
  if (!dirty.value) {
    saveState.value = 'saved';
    saveError.value = '';
    emit('dirty', false);
    return true;
  }
  return false;
}

async function flushPending() {
  if (saveLoopPromise) await saveLoopPromise;
  if (dirty.value && saveState.value !== 'conflict') {
    saveState.value = 'saved';
    await queueAutoSave();
  }
  return !dirty.value;
}

async function removeBinding(index: number) {
  if (props.readonly || props.locked) return;
  try {
    await ElMessageBox.confirm('确认移除此能力绑定？', '移除能力', {
      cancelButtonText: '取消',
      confirmButtonText: '移除',
      type: 'warning',
    });
    bindings.value.splice(index, 1);
    bindings.value.forEach((item, itemIndex) => (item.sortNo = itemIndex));
    await queueAutoSave();
  } catch {
    // Confirmation cancellation is expected.
  }
}

function handleRowCommand(
  command: 'down' | 'edit' | 'remove' | 'up',
  index: number,
) {
  const binding = bindings.value[index];
  if (!binding) return;
  if (command === 'edit') void openEdit(binding, index);
  else if (command === 'remove') void removeBinding(index);
  else void move(index, command === 'up' ? -1 : 1);
}

function bindingRowKey(row: SkillCapabilityBinding) {
  if (row.id !== undefined && row.id !== null) return `id:${row.id}`;
  let key = rowKeys.get(row);
  if (!key) {
    key = `draft:${++rowKeySequence}`;
    rowKeys.set(row, key);
  }
  return key;
}

function typeLabel(type: SkillCapabilityType) {
  return { MCP: 'MCP', PLUGIN_ITEM: '插件工具', WORKFLOW: '工作流' }[type];
}

function statusMeta(status?: string) {
  if (status === 'AVAILABLE' || !status)
    return { label: '可用', type: 'success' as const };
  if (status === 'NO_PERMISSION')
    return { label: '无权限', type: 'danger' as const };
  if (status === 'UNRESOLVED')
    return { label: '待映射', type: 'warning' as const };
  return { label: '不可用', type: 'info' as const };
}

defineExpose({
  flushPending,
  hasDirty: () => dirty.value,
});
</script>

<template>
  <section
    class="skill-capability-panel"
    :class="{ 'is-embedded': embedded }"
    v-loading="loading || locked"
    :aria-busy="loading || locked"
    :inert="locked || undefined"
  >
    <header class="skill-capability-panel__header">
      <div v-if="!embedded" class="skill-capability-panel__title">
        <h2>能力绑定</h2>
        <span>{{ bindings.length }}</span>
      </div>
      <div class="skill-capability-panel__actions">
        <button
          v-if="saveState === 'error' && dirty"
          type="button"
          class="skill-capability-panel__save-state is-error"
          @click="retryAutoSave"
        >
          {{ saveStateLabel }}
        </button>
        <span
          v-else-if="saveState !== 'saved' || !embedded"
          class="skill-capability-panel__save-state"
          :class="`is-${saveState}`"
          aria-live="polite"
        >
          {{ saveStateLabel }}
        </span>
        <ElButton
          v-access:code="'/api/v1/skill/capability'"
          :icon="Plus"
          type="primary"
          :disabled="
            readonly ||
            Boolean(loadError) ||
            dialogSaving ||
            bindings.length >= 200
          "
          @click="openCreate"
        >
          添加能力
        </ElButton>
      </div>
    </header>

    <div
      v-if="loadError || saveError"
      class="skill-capability-panel__error"
      role="alert"
    >
      <span>{{ loadError || saveError }}</span>
      <div>
        <ElButton v-if="loadError" text @click="reloadBindings">
          重新加载
        </ElButton>
        <template v-else-if="saveState === 'conflict'">
          <ElButton text @click="reloadBindings">重新加载</ElButton>
          <ElButton text type="primary" @click="reapplyLocalChanges">
            重新应用
          </ElButton>
        </template>
        <ElButton v-else-if="dirty" text @click="retryAutoSave">
          重试
        </ElButton>
      </div>
    </div>

    <ul
      v-if="bindings.length > 0"
      class="skill-capability-panel__list"
      aria-label="已绑定能力"
    >
      <li
        v-for="(row, index) in bindings"
        :key="bindingRowKey(row)"
        class="skill-capability-panel__row"
        :draggable="!readonly && !locked"
        @dragstart="startDrag(index)"
        @dragover.prevent
        @drop.prevent="dropAt(index)"
      >
        <button
          type="button"
          class="skill-capability-panel__identity"
          :disabled="readonly || locked"
          @click="openEdit(row, index)"
        >
          <span class="skill-capability-panel__type-icon" aria-hidden="true">
            {{
              row.capabilityType === 'WORKFLOW'
                ? '流'
                : row.capabilityType === 'MCP'
                  ? 'M'
                  : '插'
            }}
          </span>
          <span>
            <strong>
              {{ row.targetName || row.targetLogicalRef || '未解析目标' }}
            </strong>
            <span>{{ typeLabel(row.capabilityType) }}</span>
          </span>
        </button>
        <code class="skill-capability-panel__runtime-name">
          {{ row.runtimeName || '—' }}
        </code>
        <ElTag
          :type="statusMeta(row.targetStatus).type"
          effect="plain"
          size="small"
        >
          {{ statusMeta(row.targetStatus).label }}
        </ElTag>
        <ElSwitch
          :model-value="row.enabled"
          :disabled="readonly || locked"
          :aria-label="`${row.targetName || row.runtimeName || '能力'}启用状态`"
          @click.stop
          @change="toggleBinding(index, $event)"
        />
        <ElDropdown
          v-if="!readonly"
          trigger="click"
          :disabled="locked"
          @click.stop
          @command="handleRowCommand($event, index)"
        >
          <ElButton :icon="MoreFilled" text aria-label="能力更多操作" />
          <template #dropdown>
            <ElDropdownMenu>
              <ElDropdownItem command="edit" :icon="Edit">
                编辑
              </ElDropdownItem>
              <ElDropdownItem command="up" :disabled="index === 0">
                上移
              </ElDropdownItem>
              <ElDropdownItem
                command="down"
                :disabled="index === bindings.length - 1"
              >
                下移
              </ElDropdownItem>
              <ElDropdownItem command="remove" :icon="Delete" divided>
                移除
              </ElDropdownItem>
            </ElDropdownMenu>
          </template>
        </ElDropdown>
      </li>
    </ul>
    <ElEmpty v-else description="尚未绑定能力">
      <ElButton
        v-access:code="'/api/v1/skill/capability'"
        :disabled="readonly || Boolean(loadError) || dialogSaving"
        type="primary"
        @click="openCreate"
      >
        添加第一个能力
      </ElButton>
    </ElEmpty>

    <EasyFlowFormModal
      v-model:open="dialogOpen"
      :title="editingIndex >= 0 ? '编辑能力' : '添加能力'"
      width="560px"
      :closable="!dialogSaving"
      :confirm-loading="dialogSaving"
      confirm-text="保存"
      :submitting="dialogSaving || candidateLoading || toolsLoading"
      @confirm="confirmBinding"
    >
      <ElForm
        class="easyflow-modal-form easyflow-modal-form--compact"
        label-position="top"
      >
        <ElFormItem label="能力类型" required>
          <ElSegmented
            :model-value="form.capabilityType"
            :options="capabilityTypeOptions"
            class="skill-capability-panel__field"
            @update:model-value="changeCapabilityType"
          />
        </ElFormItem>
        <ElFormItem label="能力资源" required :error="candidateError">
          <ElSelect
            :model-value="form.targetId"
            class="skill-capability-panel__field"
            filterable
            remote
            reserve-keyword
            :loading="candidateLoading"
            :remote-method="searchCandidates"
            placeholder="搜索有权使用的资源"
            @update:model-value="changeCandidate"
          >
            <ElOption
              v-for="candidate in candidates"
              :key="candidate.targetId"
              :label="candidate.name"
              :value="candidate.targetId"
              :disabled="candidate.status !== 'AVAILABLE'"
            />
          </ElSelect>
        </ElFormItem>
        <ElFormItem
          label="运行时名称"
          required
          :error="form.runtimeName ? runtimeNameError : ''"
        >
          <ElInput v-model="form.runtimeName" maxlength="64" show-word-limit />
        </ElFormItem>
        <ElCollapse
          v-model="advancedSections"
          class="skill-capability-panel__advanced"
        >
          <ElCollapseItem name="advanced" title="高级配置">
            <template v-if="form.capabilityType === 'MCP'">
              <ElFormItem label="工具范围">
                <ElSelect
                  v-model="form.selectionMode"
                  class="skill-capability-panel__field"
                  @change="changeSelectionMode"
                >
                  <ElOption label="全部可用工具" value="ALL" />
                  <ElOption label="指定工具" value="SELECTED" />
                </ElSelect>
              </ElFormItem>
              <ElFormItem
                v-if="form.selectionMode === 'SELECTED'"
                label="选择工具"
                required
                :error="toolsError"
              >
                <div class="skill-capability-panel__tool-field">
                  <ElSelect
                    v-model="form.selectedToolNamesJson"
                    class="skill-capability-panel__field"
                    filterable
                    :loading="toolsLoading"
                    multiple
                  >
                    <ElOption
                      v-for="tool in mcpTools"
                      :key="tool"
                      :label="tool"
                      :value="tool"
                    />
                  </ElSelect>
                  <ElButton
                    v-if="toolsError && form.targetId"
                    text
                    :disabled="toolsLoading"
                    @click="loadMcpTools(form.targetId)"
                  >
                    重新加载
                  </ElButton>
                </div>
              </ElFormItem>
            </template>
            <ElFormItem v-else label="调用方式">
              <ElSelect
                v-model="form.executionMode"
                class="skill-capability-panel__field"
              >
                <ElOption label="同步返回" value="SYNC" />
                <ElOption label="异步任务" value="ASYNC" />
              </ElSelect>
            </ElFormItem>
            <ElFormItem label="执行前人工确认">
              <ElSwitch v-model="form.hitlEnabled" @change="handleHitlChange" />
            </ElFormItem>
            <template v-if="form.hitlEnabled">
              <ElFormItem label="确认提示">
                <ElInput
                  v-model="hitlPrompt"
                  type="textarea"
                  :rows="3"
                  maxlength="1000"
                  show-word-limit
                  placeholder="说明需要确认的操作"
                />
              </ElFormItem>
              <ElFormItem label="确认窗口标题">
                <ElInput v-model="hitlTitle" maxlength="128" />
              </ElFormItem>
              <div class="skill-capability-panel__advanced-grid">
                <ElFormItem label="确认按钮">
                  <ElInput v-model="hitlConfirmLabel" maxlength="32" />
                </ElFormItem>
                <ElFormItem label="取消按钮">
                  <ElInput v-model="hitlCancelLabel" maxlength="32" />
                </ElFormItem>
              </div>
            </template>
            <div class="skill-capability-panel__advanced-grid">
              <ElFormItem label="超时时间（毫秒）" :error="timeoutError">
                <ElInputNumber
                  v-model="timeoutMs"
                  :min="100"
                  :max="300_000"
                  :step="100"
                  controls-position="right"
                  placeholder="使用默认值"
                />
              </ElFormItem>
              <ElFormItem label="失败重试次数" :error="retryError">
                <ElInputNumber
                  v-model="retryCount"
                  :min="0"
                  :max="10"
                  controls-position="right"
                  placeholder="使用默认值"
                />
              </ElFormItem>
            </div>
            <ElFormItem label="只读调用">
              <ElSwitch v-model="readOnlyOption" />
            </ElFormItem>
          </ElCollapseItem>
        </ElCollapse>
      </ElForm>
    </EasyFlowFormModal>
  </section>
</template>

<style scoped>
.skill-capability-panel {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: hsl(var(--surface-panel));
  border: 1px solid hsl(var(--line-subtle));
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-subtle);
}

.skill-capability-panel.is-embedded {
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.skill-capability-panel.is-embedded .skill-capability-panel__header {
  justify-content: flex-end;
  min-height: 56px;
  padding-block: var(--space-3);
}

.skill-capability-panel__header {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--space-6);
}

.skill-capability-panel__error {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
  padding: var(--space-3) var(--space-6);
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 8%);
}

.skill-capability-panel__error > div {
  display: flex;
  gap: var(--space-1);
  align-items: center;
}

.skill-capability-panel__header {
  min-height: 64px;
  border-bottom: 1px solid hsl(var(--line-subtle));
}

.skill-capability-panel__title,
.skill-capability-panel__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.skill-capability-panel__header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.skill-capability-panel__title > span {
  display: grid;
  min-width: 24px;
  height: 24px;
  place-items: center;
  font-size: 12px;
  color: hsl(var(--text-muted));
  background: hsl(var(--surface-contrast-soft));
  border-radius: var(--radius-pill);
}

.skill-capability-panel__save-state {
  padding: 0 var(--space-1);
  font-size: 12px;
  color: hsl(var(--text-muted));
  background: transparent;
  border: 0;
}

.skill-capability-panel__save-state.is-saving {
  color: hsl(var(--warning));
}

.skill-capability-panel__save-state.is-error,
.skill-capability-panel__save-state.is-conflict {
  color: hsl(var(--destructive));
}

.skill-capability-panel__save-state.is-error {
  cursor: pointer;
}

.skill-capability-panel__list {
  flex: 1;
  min-height: 0;
  padding: 0;
  margin: 0;
  overflow: auto;
  list-style: none;
}

.skill-capability-panel__row {
  display: grid;
  grid-template-columns: minmax(220px, 1.6fr) minmax(144px, 1fr) auto auto 40px;
  gap: var(--space-4);
  align-items: center;
  min-height: 72px;
  padding: var(--space-2) var(--space-6);
  border-bottom: 1px solid hsl(var(--line-subtle));
  transition: background-color var(--motion-duration-fast)
    var(--motion-ease-standard);
}

.skill-capability-panel__row:hover {
  background: hsl(var(--surface-subtle));
}

.skill-capability-panel__row[draggable='true'] {
  cursor: grab;
}

.skill-capability-panel__row[draggable='true']:active {
  cursor: grabbing;
}

.skill-capability-panel__identity {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  min-width: 0;
  padding: 0;
  color: hsl(var(--foreground));
  text-align: left;
  cursor: pointer;
  background: transparent;
  border: 0;
  border-radius: var(--radius-control);
}

.skill-capability-panel__identity:disabled {
  cursor: default;
}

.skill-capability-panel__identity:focus-visible {
  outline: 2px solid hsl(var(--primary) / 72%);
  outline-offset: 3px;
}

.skill-capability-panel__identity > span:last-child {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.skill-capability-panel__identity strong,
.skill-capability-panel__identity span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skill-capability-panel__identity span {
  font-size: 12px;
  color: hsl(var(--text-muted));
}

.skill-capability-panel__type-icon {
  display: grid;
  flex: none;
  width: 36px;
  height: 36px;
  place-items: center;
  font-weight: 650;
  color: hsl(var(--nav-item-active-foreground)) !important;
  background: hsl(var(--nav-item-active));
  border-radius: var(--radius-control);
}

.skill-capability-panel__runtime-name {
  overflow: hidden;
  font-size: 12px;
  color: hsl(var(--text-muted));
  text-overflow: ellipsis;
  white-space: nowrap;
  background: transparent;
}

.skill-capability-panel__field {
  width: 100%;
}

.skill-capability-panel__tool-field {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  width: 100%;
}

.skill-capability-panel__tool-field .skill-capability-panel__field {
  flex: 1;
  min-width: 0;
}

.skill-capability-panel__advanced {
  border-top: 1px solid hsl(var(--line-subtle));
}

.skill-capability-panel__advanced :deep(.el-collapse-item__wrap) {
  border-bottom: 0;
}

.skill-capability-panel__advanced-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

.skill-capability-panel__advanced-grid :deep(.el-input-number) {
  width: 100%;
}

.skill-capability-panel :deep(.el-empty) {
  flex: 1;
}

@media (max-width: 720px) {
  .skill-capability-panel__header {
    flex-direction: column;
    align-items: flex-start;
  }

  .skill-capability-panel__actions {
    justify-content: flex-end;
    width: 100%;
  }

  .skill-capability-panel__row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--space-2);
    padding-inline: var(--space-4);
  }

  .skill-capability-panel__runtime-name {
    display: none;
  }

  .skill-capability-panel__row > :deep(.el-tag) {
    display: none;
  }

  .skill-capability-panel__advanced-grid {
    grid-template-columns: 1fr;
  }
}
</style>
