<script setup lang="ts">
import type { SkillCategory, SkillInfo } from './types';

import {
  computed,
  onBeforeUnmount,
  onMounted,
  reactive,
  ref,
  watch,
} from 'vue';
import {
  onBeforeRouteLeave,
  onBeforeRouteUpdate,
  useRoute,
  useRouter,
} from 'vue-router';

import {
  ArrowLeft,
  Connection,
  Delete,
  Edit,
  MoreFilled,
  Promotion,
  Upload,
} from '@element-plus/icons-vue';
import {
  ElButton,
  ElDialog,
  ElDropdown,
  ElDropdownItem,
  ElDropdownMenu,
  ElInput,
  ElMessage,
  ElMessageBox,
  ElResult,
  ElTag,
} from 'element-plus';

import { hasPermission } from '#/api/common/hasPermission';
import {
  navigateBackToList,
  resolveListReturnPath,
} from '#/router/list-return-context';
import {
  canAiResourceDelete,
  canAiResourceOffline,
  canAiResourcePublish,
  canAiResourceRepublish,
  resolveAiResourceDisplayStatus,
} from '#/views/ai/shared/publish-status';

import {
  getSkillCategories,
  getSkillDetail,
  submitSkillDeleteApproval,
  submitSkillOfflineApproval,
  submitSkillPublishApproval,
} from './api';
import { isSkillAccessDeniedError } from './skill-api-error';
import { readFrontmatterScalar, splitSkillMarkdown } from './skill-markdown';
import SkillResourceWorkbench from './SkillResourceWorkbench.vue';
import SkillSettingsDialog from './SkillSettingsDialog.vue';
import SkillToolBindingDialog from './SkillToolBindingDialog.vue';

const route = useRoute();
const router = useRouter();
const skillId = computed(() => String(route.params.id || ''));
const loading = ref(false);
const saving = ref(false);
const validating = ref(false);
const publishing = ref(false);
const lifecycleAction = ref<'delete' | 'offline'>();
const loadError = ref('');
const loadAccessDenied = ref(false);
const resourceDirty = ref(false);
const settingsDialogOpen = ref(false);
const toolBindingDialogOpen = ref(false);
const publishDialogOpen = ref(false);
const publishReason = ref('');
const categories = ref<SkillCategory[]>([]);
const categoryLoading = ref(false);
const categoryLoadError = ref('');
const resourceWorkbenchRef = ref<InstanceType<typeof SkillResourceWorkbench>>();
let allowOperationNavigation = false;
let initRequest = 0;
let categoryRequest = 0;
const skill = reactive<SkillInfo>({
  description: '',
  displayName: '',
  name: '',
  skillContent: '',
  visibilityScope: 'PRIVATE',
});

const operationLocked = computed(() =>
  Boolean(
    saving.value ||
      validating.value ||
      publishing.value ||
      lifecycleAction.value,
  ),
);
const detailTitle = computed(
  () =>
    skill.displayName ||
    String(route.query.navTitle || '') ||
    skill.name ||
    '技能',
);
const displayStatus = computed(() =>
  resolveAiResourceDisplayStatus(
    skill.displayPublishStatus,
    skill.publishStatus,
  ),
);
const canManage = computed(
  () => hasPermission(['/api/v1/skill/update']) && skill.manageable !== false,
);
const canEditFiles = computed(
  () => canManage.value && hasPermission(['/api/v1/skill/file']),
);
const canManageTools = computed(
  () => canManage.value && hasPermission(['/api/v1/skill/save']),
);
const canSubmitPublish = computed(
  () =>
    canManage.value &&
    hasPermission(['/api/v1/skill/submitPublishApproval']) &&
    (canAiResourcePublish(skill.displayPublishStatus, skill.publishStatus) ||
      canAiResourceRepublish(skill.displayPublishStatus, skill.publishStatus)),
);
const canSubmitOffline = computed(
  () =>
    canManage.value &&
    hasPermission(['/api/v1/skill/submitOfflineApproval']) &&
    canAiResourceOffline(skill.displayPublishStatus, skill.publishStatus),
);
const canSubmitDelete = computed(
  () =>
    canManage.value &&
    hasPermission(['/api/v1/skill/submitDeleteApproval']) &&
    canAiResourceDelete(skill.displayPublishStatus, skill.publishStatus),
);
const statusMeta = computed(
  () =>
    ({
      DELETE_PENDING: { label: '删除中', type: 'danger' as const },
      DRAFT: { label: '草稿', type: 'info' as const },
      OFFLINE: { label: '已下线', type: 'info' as const },
      OFFLINE_PENDING: { label: '下线中', type: 'warning' as const },
      PUBLISHED: { label: '已发布', type: 'success' as const },
      PUBLISH_PENDING: { label: '待审核', type: 'warning' as const },
    })[displayStatus.value],
);

onMounted(() => {
  window.addEventListener('beforeunload', handleBeforeUnload);
  window.addEventListener('keydown', handleSaveShortcut);
  void init();
});
watch(skillId, init);
onBeforeUnmount(() => {
  window.removeEventListener('beforeunload', handleBeforeUnload);
  window.removeEventListener('keydown', handleSaveShortcut);
});
onBeforeRouteLeave(confirmNavigation);
onBeforeRouteUpdate(confirmNavigation);

async function init() {
  if (!skillId.value || skillId.value === 'new') {
    await returnToListDuringOperation();
    return;
  }
  const request = ++initRequest;
  loading.value = true;
  loadError.value = '';
  try {
    const [detailResponse] = await Promise.all([
      getSkillDetail(skillId.value),
      loadCategories(),
    ]);
    if (request !== initRequest) return;
    if (detailResponse.errorCode !== 0) throw detailResponse;
    resetSkill(detailResponse.data);
    resourceDirty.value = false;
    loadAccessDenied.value = false;
  } catch (error) {
    if (request !== initRequest) return;
    loadAccessDenied.value = isSkillAccessDeniedError(error);
    loadError.value = loadAccessDenied.value
      ? '当前账号没有查看权限'
      : '加载失败，请重试';
  } finally {
    if (request === initRequest) loading.value = false;
  }
}

async function loadCategories() {
  const request = ++categoryRequest;
  categoryLoading.value = true;
  categoryLoadError.value = '';
  try {
    const response = await getSkillCategories();
    if (request !== categoryRequest) return;
    if (response.errorCode !== 0) throw response;
    categories.value = response.data || [];
  } catch {
    if (request !== categoryRequest) return;
    categories.value = [];
    categoryLoadError.value = '分类加载失败，请重试';
  } finally {
    if (request === categoryRequest) categoryLoading.value = false;
  }
}

function resetSkill(next: SkillInfo) {
  for (const key of Object.keys(skill)) Reflect.deleteProperty(skill, key);
  Object.assign(skill, {
    description: '',
    displayName: '',
    name: '',
    skillContent: '',
    visibilityScope: 'PRIVATE',
    ...next,
  });
}

async function backToList() {
  await navigateBackToList(router, route.query, ['/ai/skill'], '/ai/skill');
}

async function returnToListDuringOperation() {
  allowOperationNavigation = true;
  try {
    await router.replace(
      resolveListReturnPath(router, route.query, ['/ai/skill'], '/ai/skill'),
    );
  } finally {
    allowOperationNavigation = false;
  }
}

async function confirmNavigation() {
  if (operationLocked.value && !allowOperationNavigation) return false;
  if (!resourceDirty.value) return true;
  try {
    await ElMessageBox.confirm('当前有未保存修改，确认离开？', '离开编辑', {
      cancelButtonText: '继续编辑',
      confirmButtonText: '放弃修改',
      type: 'warning',
    });
    return true;
  } catch {
    return false;
  }
}

async function saveFiles(showFeedback = true) {
  if (saving.value || !resourceDirty.value) return !resourceDirty.value;
  saving.value = true;
  try {
    if (!(await resourceWorkbenchRef.value?.saveAll())) return false;
    const content = resourceWorkbenchRef.value?.getSkillContent?.();
    if (typeof content === 'string') syncSkillMetadata(content);
    resourceDirty.value = false;
    if (showFeedback) ElMessage.success('已保存');
    return true;
  } finally {
    saving.value = false;
  }
}

function syncSkillMetadata(content: string) {
  const parts = splitSkillMarkdown(content);
  skill.skillContent = content;
  skill.name = readFrontmatterScalar(parts.frontmatter, 'name');
  skill.description = readFrontmatterScalar(parts.frontmatter, 'description');
}

async function openPublishDialog() {
  if (!canSubmitPublish.value || operationLocked.value) return;
  if (resourceDirty.value && !(await saveFiles(false))) return;
  validating.value = true;
  try {
    if (!(await resourceWorkbenchRef.value?.validateAll())) return;
    publishReason.value = '';
    publishDialogOpen.value = true;
  } finally {
    validating.value = false;
  }
}

async function confirmPublish() {
  const reason = publishReason.value.trim();
  if (!reason) {
    ElMessage.warning('请输入发布说明');
    return;
  }
  if (reason.length > 500) return;
  publishing.value = true;
  try {
    const response = await submitSkillPublishApproval(skillId.value, reason);
    if (response.errorCode !== 0) return;
    publishDialogOpen.value = false;
    ElMessage.success(response.message || '已提交发布');
    await init();
  } finally {
    publishing.value = false;
  }
}

function handleMoreCommand(
  command: 'delete' | 'offline' | 'settings' | 'tools',
) {
  if (command === 'settings') settingsDialogOpen.value = true;
  if (command === 'tools') toolBindingDialogOpen.value = true;
  if (command === 'offline') void offline();
  if (command === 'delete') void remove();
}

async function offline() {
  if (!canSubmitOffline.value || operationLocked.value) return;
  try {
    await ElMessageBox.confirm('下线后将不可继续使用，确认下线？', '下线技能', {
      cancelButtonText: '取消',
      confirmButtonText: '下线',
      type: 'warning',
    });
  } catch {
    return;
  }
  lifecycleAction.value = 'offline';
  try {
    if (resourceDirty.value && !(await saveFiles(false))) return;
    const response = await submitSkillOfflineApproval(skillId.value);
    if (response.errorCode === 0) await init();
  } finally {
    lifecycleAction.value = undefined;
  }
}

async function remove() {
  if (!canSubmitDelete.value || operationLocked.value) return;
  try {
    await ElMessageBox.confirm('删除后无法恢复，确认删除？', '删除技能', {
      cancelButtonText: '取消',
      confirmButtonText: '删除',
      type: 'warning',
    });
  } catch {
    return;
  }
  lifecycleAction.value = 'delete';
  try {
    if (resourceDirty.value && !(await saveFiles(false))) return;
    const response = await submitSkillDeleteApproval(skillId.value);
    if (response.errorCode !== 0) return;
    await (response.data === null || response.data === undefined
      ? returnToListDuringOperation()
      : init());
  } finally {
    lifecycleAction.value = undefined;
  }
}

function handleSettingsSaved(next: SkillInfo) {
  Object.assign(skill, next);
}

function handleToolBindingsSaved(
  bindings: NonNullable<SkillInfo['toolBindings']>,
) {
  skill.toolBindings = bindings;
  skill.toolCount = bindings.reduce(
    (total, binding) =>
      total + (binding.toolType === 'MCP' ? binding.mcpToolCount || 0 : 1),
    0,
  );
  skill.hasToolUpdate = true;
}

function handleBeforeUnload(event: BeforeUnloadEvent) {
  if (!operationLocked.value && !resourceDirty.value) return;
  event.preventDefault();
  event.returnValue = '';
}

function handleSaveShortcut(event: KeyboardEvent) {
  if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 's')
    return;
  event.preventDefault();
  if (resourceDirty.value && !operationLocked.value) void saveFiles();
}
</script>

<template>
  <div class="skill-detail-page" v-loading="loading">
    <header class="skill-detail-page__header">
      <div class="skill-detail-page__identity">
        <ElButton
          :icon="ArrowLeft"
          circle
          size="small"
          text
          aria-label="返回技能列表"
          @click="backToList"
        />
        <span class="skill-detail-page__mark" aria-hidden="true">S</span>
        <h1>{{ detailTitle }}</h1>
        <ElTag
          v-if="!loadError"
          :type="statusMeta.type"
          effect="plain"
          size="small"
        >
          {{ statusMeta.label }}
        </ElTag>
        <ElTag v-if="!loadError" type="primary" effect="plain" size="small">
          {{
            skill.visibilityScope === 'PUBLIC'
              ? '全部'
              : skill.visibilityScope === 'DEPT'
                ? '部门'
                : '个人'
          }}
        </ElTag>
      </div>
      <div v-if="!loadError" class="skill-detail-page__actions">
        <span
          class="skill-detail-page__save-state"
          :class="{ 'is-dirty': resourceDirty }"
          aria-live="polite"
        >
          {{ saving ? '保存中' : resourceDirty ? '未保存' : '已保存' }}
        </span>
        <ElButton
          v-if="canEditFiles"
          size="small"
          :loading="saving"
          :disabled="!resourceDirty || operationLocked"
          @click="saveFiles()"
        >
          保存
        </ElButton>
        <ElButton
          v-if="canSubmitPublish"
          type="primary"
          size="small"
          :icon="Promotion"
          :loading="validating || publishing"
          :disabled="Boolean(lifecycleAction) || saving"
          @click="openPublishDialog"
        >
          {{
            canAiResourceRepublish(
              skill.displayPublishStatus,
              skill.publishStatus,
            )
              ? '重新发布'
              : '发布'
          }}
        </ElButton>
        <ElDropdown
          v-if="canManage || canSubmitOffline || canSubmitDelete"
          :disabled="operationLocked"
          @command="handleMoreCommand"
        >
          <ElButton :icon="MoreFilled" aria-label="更多操作" size="small" />
          <template #dropdown>
            <ElDropdownMenu>
              <ElDropdownItem v-if="canManage" command="settings" :icon="Edit">
                基本信息
              </ElDropdownItem>
              <ElDropdownItem
                v-if="canManageTools"
                command="tools"
                :icon="Connection"
              >
                工具绑定
              </ElDropdownItem>
              <ElDropdownItem
                v-if="canSubmitOffline"
                command="offline"
                :icon="Upload"
              >
                下线
              </ElDropdownItem>
              <ElDropdownItem
                v-if="canSubmitDelete"
                command="delete"
                :icon="Delete"
                divided
              >
                删除
              </ElDropdownItem>
            </ElDropdownMenu>
          </template>
        </ElDropdown>
      </div>
    </header>

    <main class="skill-detail-page__content">
      <ElResult
        v-if="loadError"
        :icon="loadAccessDenied ? 'warning' : 'error'"
        :title="loadAccessDenied ? '无权查看技能' : '技能加载失败'"
        :sub-title="loadError"
      >
        <template #extra>
          <ElButton @click="backToList">返回列表</ElButton>
          <ElButton v-if="!loadAccessDenied" type="primary" @click="init">
            重新加载
          </ElButton>
        </template>
      </ElResult>
      <SkillResourceWorkbench
        v-else
        ref="resourceWorkbenchRef"
        :skill-id="skillId"
        :readonly="!canEditFiles || operationLocked"
        @dirty="resourceDirty = $event"
        @request-save="saveFiles()"
      />
    </main>

    <SkillSettingsDialog
      v-if="canManage"
      v-model="settingsDialogOpen"
      :categories="categories"
      :categories-loading="categoryLoading"
      :category-load-error="categoryLoadError"
      :skill="skill"
      @retry-categories="loadCategories"
      @saved="handleSettingsSaved"
    />

    <SkillToolBindingDialog
      v-if="canManageTools"
      v-model="toolBindingDialogOpen"
      :bindings="skill.toolBindings"
      :has-tool-update="skill.hasToolUpdate"
      :skill-id="skillId"
      @saved="handleToolBindingsSaved"
    />

    <ElDialog
      v-model="publishDialogOpen"
      width="min(520px, calc(100vw - 32px))"
      :close-on-click-modal="false"
      :show-close="!publishing"
      title="发布技能"
    >
      <div class="skill-publish-label">发布说明</div>
      <ElInput
        id="skill-publish-reason"
        aria-label="发布说明"
        v-model="publishReason"
        type="textarea"
        :rows="6"
        maxlength="500"
        show-word-limit
        placeholder="填写本次发布内容"
      />
      <template #footer>
        <ElButton :disabled="publishing" @click="publishDialogOpen = false">
          取消
        </ElButton>
        <ElButton type="primary" :loading="publishing" @click="confirmPublish">
          发布
        </ElButton>
      </template>
    </ElDialog>
  </div>
</template>

<style scoped>
.skill-detail-page {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-4);
  background: hsl(var(--background));
}

.skill-detail-page__header {
  display: flex;
  gap: var(--space-4);
  align-items: center;
  justify-content: space-between;
  min-height: 48px;
}

.skill-detail-page__identity,
.skill-detail-page__actions {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  min-width: 0;
}

.skill-detail-page__identity h1 {
  max-width: min(360px, 32vw);
  margin: 0 var(--space-1) 0 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 18px;
  font-weight: 650;
  line-height: 1.4;
  white-space: nowrap;
}

.skill-detail-page__mark {
  display: grid;
  width: 32px;
  height: 32px;
  place-items: center;
  color: hsl(var(--nav-item-active-foreground));
  background: hsl(var(--nav-item-active));
  border-radius: var(--radius-control);
}

.skill-detail-page__save-state {
  margin-right: var(--space-1);
  font-size: 12px;
  color: hsl(var(--success));
}

.skill-detail-page__save-state.is-dirty {
  color: hsl(var(--warning));
}

.skill-detail-page__content {
  display: flex;
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.skill-detail-page__content :deep(.el-result) {
  margin: auto;
}

.skill-publish-label {
  display: block;
  margin-bottom: var(--space-2);
  font-weight: 600;
}

@media (max-width: 760px) {
  .skill-detail-page {
    gap: var(--space-2);
    padding: var(--space-2);
  }

  .skill-detail-page__header {
    align-items: stretch;
    flex-direction: column;
  }

  .skill-detail-page__actions {
    justify-content: flex-end;
    width: 100%;
  }

  .skill-detail-page__identity h1 {
    max-width: min(52vw, 320px);
  }
}
</style>
