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

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

import {
  ArrowLeft,
  Delete,
  Edit,
  MoreFilled,
  Promotion,
  Upload,
} from '@element-plus/icons-vue';
import {
  ElButton,
  ElDropdown,
  ElDropdownItem,
  ElDropdownMenu,
  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 SkillCapabilityPanel from './SkillCapabilityPanel.vue';
import SkillResourceWorkbench from './SkillResourceWorkbench.vue';
import SkillSettingsDialog from './SkillSettingsDialog.vue';

const route = useRoute();
const router = useRouter();
const skillId = computed(() => String(route.params.id || 'new'));
const isNew = computed(() => skillId.value === 'new');
const loading = ref(false);
const saving = ref(false);
const publishing = ref(false);
const validating = ref(false);
const lifecycleAction = ref<'delete' | 'offline' | undefined>();
const loadError = ref('');
const loadAccessDenied = ref(false);
const activeSection = ref<'capability' | 'resources'>('resources');
const settingsDialogOpen = ref(false);
const categories = ref<SkillCategory[]>([]);
const categoryLoading = ref(false);
const categoryLoadError = ref('');
const resourceWorkbenchRef = ref<InstanceType<typeof SkillResourceWorkbench>>();
const capabilityPanelRef = ref<InstanceType<typeof SkillCapabilityPanel>>();
const resourceDirty = ref(false);
const capabilityDirty = ref(false);
let allowOperationNavigation = false;
let initRequest = 0;

const skill = reactive<SkillInfo>({
  description: '',
  displayName: '',
  enabled: true,
  name: '',
  skillContent: '---\nname: ""\ndescription: ""\n---\n\n# Instructions\n',
  visibilityScope: 'PRIVATE',
});
const operationLocked = computed(
  () =>
    saving.value ||
    validating.value ||
    publishing.value ||
    Boolean(lifecycleAction.value),
);

const canViewCapabilities = computed(() =>
  hasPermission(['/api/v1/skill/capability']),
);
const hasUnsavedChanges = computed(
  () => resourceDirty.value || capabilityDirty.value,
);
const detailTitle = computed(() => {
  if (loadError.value)
    return loadAccessDenied.value ? '无权查看 Skill' : '无法加载 Skill';
  const routeTitle = String(route.query.navTitle || '').trim();
  return skill.displayName || routeTitle || skill.name || 'Skill';
});
const status = computed(() =>
  resolveAiResourceDisplayStatus(
    skill.displayPublishStatus,
    skill.publishStatus,
  ),
);
const canManage = computed(
  () => hasPermission(['/api/v1/skill/update']) && skill.manageable !== false,
);
const canManageResource = computed(
  () => !isNew.value && skill.manageable !== false,
);
const canSubmitPublish = computed(
  () =>
    canManageResource.value &&
    hasPermission(['/api/v1/skill/submitPublishApproval']) &&
    (canAiResourcePublish(skill.displayPublishStatus, skill.publishStatus) ||
      canAiResourceRepublish(skill.displayPublishStatus, skill.publishStatus)),
);
const canSubmitOffline = computed(
  () =>
    canManageResource.value &&
    hasPermission(['/api/v1/skill/submitOfflineApproval']) &&
    canAiResourceOffline(skill.displayPublishStatus, skill.publishStatus),
);
const canSubmitDelete = computed(
  () =>
    canManageResource.value &&
    hasPermission(['/api/v1/skill/submitDeleteApproval']) &&
    canAiResourceDelete(skill.displayPublishStatus, skill.publishStatus),
);
const canEditFiles = computed(
  () => skill.manageable !== false && hasPermission(['/api/v1/skill/file']),
);
const canBindCapabilities = computed(
  () => skill.manageable !== false && canViewCapabilities.value,
);
const headerSaveState = computed(() => {
  if (saving.value) return '保存中';
  if (resourceDirty.value) return '有未保存修改';
  if (capabilityDirty.value) return '能力保存中';
  return '已保存';
});
const statusMeta = computed(() => {
  switch (status.value) {
    case 'DELETE_PENDING': {
      return { label: '删除审批中', type: 'danger' as const };
    }
    case 'OFFLINE': {
      return { label: '已下线', type: 'info' as const };
    }
    case 'OFFLINE_PENDING': {
      return { label: '下线审批中', type: 'warning' as const };
    }
    case 'PUBLISH_PENDING': {
      return { label: '发布审批中', type: 'warning' as const };
    }
    case 'PUBLISHED': {
      return { label: '已发布', type: 'success' as const };
    }
    default: {
      return { label: '草稿', type: 'info' as const };
    }
  }
});

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

async function confirmUnsavedNavigation() {
  if (operationLocked.value && !allowOperationNavigation) {
    ElMessage.info('操作处理中，请稍候');
    return false;
  }
  if (!hasUnsavedChanges.value) return true;
  try {
    await ElMessageBox.confirm(
      '当前仍有未保存内容，离开后修改会丢失。',
      '离开 Skill',
      {
        cancelButtonText: '继续编辑',
        confirmButtonText: '放弃修改',
        type: 'warning',
      },
    );
    return true;
  } catch {
    return false;
  }
}

onBeforeRouteLeave(confirmUnsavedNavigation);
onBeforeRouteUpdate(confirmUnsavedNavigation);

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

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

async function init() {
  const request = ++initRequest;
  loading.value = true;
  loadError.value = '';
  loadAccessDenied.value = false;
  try {
    if (isNew.value) {
      await returnToSkillListDuringOperation();
      return;
    }
    await loadCategories(request);
    if (request !== initRequest) return;
    const res = await getSkillDetail(skillId.value);
    if (request !== initRequest) return;
    if (res.errorCode === 0) {
      resetSkill(res.data);
    } else {
      if (isSkillAccessDeniedError(res)) throw res;
      throw new Error(res.message || 'Skill 加载失败');
    }
    resourceDirty.value = false;
    capabilityDirty.value = false;
  } catch (error) {
    if (request === initRequest) {
      loadAccessDenied.value = isSkillAccessDeniedError(error);
      loadError.value = loadAccessDenied.value
        ? '当前账号没有查看此 Skill 的权限'
        : 'Skill 加载失败，请重试';
    }
  } finally {
    if (request === initRequest) loading.value = false;
  }
}

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

function resetSkill(next: SkillInfo) {
  for (const key of Object.keys(skill)) delete skill[key];
  Object.assign(skill, next);
}

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

async function refreshSavedDetail() {
  try {
    const res = await getSkillDetail(skillId.value);
    if (res.errorCode !== 0) return false;
    resetSkill(res.data);
    return true;
  } catch {
    return false;
  }
}

function locateCapabilitySection() {
  if (!canViewCapabilities.value) {
    activeSection.value = 'resources';
    ElMessage.warning('当前没有查看能力绑定的权限');
    return;
  }
  activeSection.value = 'capability';
}

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

function handleCapabilitySaved(bindings: unknown[]) {
  skill.capabilityCount = bindings.length;
}

function handleLifecycleCommand(command: 'delete' | 'offline' | 'settings') {
  if (command === 'settings') {
    settingsDialogOpen.value = true;
    return;
  }
  if (command === 'offline') void offline();
  else void remove();
}

async function saveFiles(allowLifecycleAction = false, showFeedback = true) {
  if (saving.value || (lifecycleAction.value && !allowLifecycleAction))
    return false;
  if (!resourceDirty.value) return true;
  saving.value = true;
  try {
    await nextTick();
    if (!canEditFiles.value) {
      ElMessage.warning('文件管理权限已变化，请刷新后重试');
      return false;
    }
    if (!(await resourceWorkbenchRef.value?.saveAll())) return false;
    const latestSkillContent = resourceWorkbenchRef.value?.getSkillContent?.();
    if (typeof latestSkillContent === 'string') {
      skill.skillContent = latestSkillContent;
      syncSkillMetadataFromContent(latestSkillContent);
    }
    const refreshed = await refreshSavedDetail();
    resourceDirty.value = false;
    if (showFeedback && refreshed) ElMessage.success('修改已保存');
    else if (!refreshed) {
      ElMessage.warning('修改已保存，最新状态加载失败，请刷新页面');
    }
    return true;
  } finally {
    saving.value = false;
  }
}

async function flushPendingChanges(allowLifecycleAction = false) {
  if (resourceDirty.value && !(await saveFiles(allowLifecycleAction, false)))
    return false;
  const panel = capabilityPanelRef.value;
  if (capabilityDirty.value || panel?.hasDirty()) {
    if (!canBindCapabilities.value || !panel) {
      ElMessage.warning('能力绑定权限或页面状态已变化，请刷新后重试');
      return false;
    }
    if (!(await panel.flushPending())) {
      activeSection.value = 'capability';
      ElMessage.warning('能力绑定尚未保存，请处理后重试');
      return false;
    }
  }
  capabilityDirty.value = false;
  return true;
}

async function validateSkillContentInternal(savePendingChanges: boolean) {
  if (
    validating.value ||
    lifecycleAction.value ||
    (publishing.value && savePendingChanges)
  )
    return false;
  if (isNew.value) {
    ElMessage.info('请先保存 Skill，再执行完整校验');
    return false;
  }
  validating.value = true;
  try {
    await nextTick();
    if (
      savePendingChanges &&
      hasUnsavedChanges.value &&
      !(await flushPendingChanges())
    ) {
      return false;
    }
    activeSection.value = 'resources';
    await new Promise((resolve) => requestAnimationFrame(resolve));
    return (await resourceWorkbenchRef.value?.validateAll()) ?? false;
  } finally {
    validating.value = false;
  }
}

async function publish() {
  if (
    publishing.value ||
    saving.value ||
    validating.value ||
    lifecycleAction.value
  )
    return;
  if (isNew.value) {
    ElMessage.warning('请先保存 Skill');
    return;
  }
  if (!canSubmitPublish.value) return;
  publishing.value = true;
  try {
    await nextTick();
    if (hasUnsavedChanges.value && !(await flushPendingChanges())) return;
    if (!(await validateSkillContentInternal(false))) return;
    if (!canSubmitPublish.value) {
      ElMessage.warning('发布权限或状态已变化，请刷新后重试');
      return;
    }
    const republish = canAiResourceRepublish(
      skill.displayPublishStatus,
      skill.publishStatus,
    );
    try {
      await ElMessageBox.confirm(
        republish
          ? '发布级校验已通过，确认将当前内容作为新版本提交审批？'
          : '发布级校验已通过，确认提交发布审批？',
        republish ? '重新发布' : '发布 Skill',
        {
          cancelButtonText: '取消',
          confirmButtonText: '提交审批',
          type: 'info',
        },
      );
    } catch {
      return;
    }
    const res = await submitSkillPublishApproval(skillId.value);
    if (res.errorCode === 0) {
      ElMessage.success(res.message || '已提交发布审批');
      await init();
    }
  } finally {
    publishing.value = false;
  }
}

async function offline() {
  if (
    lifecycleAction.value ||
    publishing.value ||
    saving.value ||
    validating.value
  )
    return;
  if (!canSubmitOffline.value) return;
  lifecycleAction.value = 'offline';
  try {
    await nextTick();
    try {
      await ElMessageBox.confirm(
        hasUnsavedChanges.value
          ? '将先保存当前修改，再提交下线审批。下线后，当前发布版本将停止使用。'
          : '下线后，当前发布版本将停止使用。',
        '下线 Skill',
        {
          cancelButtonText: '取消',
          confirmButtonText: '提交审批',
          type: 'warning',
        },
      );
    } catch {
      return;
    }
    if (hasUnsavedChanges.value && !(await flushPendingChanges(true))) return;
    const res = await submitSkillOfflineApproval(skillId.value);
    if (res.errorCode === 0) {
      ElMessage.success(res.message || '已提交下线审批');
      await init();
    }
  } finally {
    lifecycleAction.value = undefined;
  }
}

async function remove() {
  if (
    lifecycleAction.value ||
    publishing.value ||
    saving.value ||
    validating.value
  )
    return;
  if (!canSubmitDelete.value) return;
  lifecycleAction.value = 'delete';
  try {
    await nextTick();
    try {
      await ElMessageBox.confirm(
        hasUnsavedChanges.value
          ? '将先保存当前修改，再删除此 Skill。删除后将无法恢复。'
          : '删除后将无法继续编辑或使用此 Skill。',
        '删除 Skill',
        {
          cancelButtonText: '取消',
          confirmButtonText: '确认删除',
          type: 'warning',
        },
      );
    } catch {
      return;
    }
    if (hasUnsavedChanges.value && !(await flushPendingChanges(true))) return;
    const res = await submitSkillDeleteApproval(skillId.value);
    if (res.errorCode === 0) {
      if (res.data === null || res.data === undefined) {
        ElMessage.success(res.message || 'Skill 已删除');
        await returnToSkillListDuringOperation();
      } else {
        ElMessage.success(res.message || '已提交删除审批');
        await init();
      }
    }
  } finally {
    lifecycleAction.value = undefined;
  }
}

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

function handleSaveShortcut(event: KeyboardEvent) {
  if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 's') {
    return;
  }
  event.preventDefault();
  if (canEditFiles.value && 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__title-group">
        <ElButton
          :icon="ArrowLeft"
          text
          :disabled="operationLocked"
          aria-label="返回 Skill 列表"
          @click="backToSkillList"
        />
        <div class="skill-detail-page__title">
          <div>
            <h1>
              {{ detailTitle }}
            </h1>
            <ElTag
              v-if="!loadError"
              :type="statusMeta.type"
              size="small"
              effect="plain"
            >
              {{ statusMeta.label }}
            </ElTag>
            <ElTag
              v-if="!loadError && !isNew && !canManage"
              type="info"
              size="small"
              effect="plain"
            >
              只读
            </ElTag>
          </div>
        </div>
      </div>
      <div v-if="!loadError" class="skill-detail-page__actions">
        <span
          class="skill-detail-page__save-state"
          :class="{ 'is-dirty': hasUnsavedChanges }"
          aria-live="polite"
        >
          {{ headerSaveState }}
        </span>
        <ElButton
          v-if="canEditFiles"
          :loading="saving"
          :disabled="
            !resourceDirty ||
            publishing ||
            validating ||
            Boolean(lifecycleAction)
          "
          @click="saveFiles()"
        >
          保存
        </ElButton>
        <ElButton
          v-if="canSubmitPublish"
          v-access:code="'/api/v1/skill/submitPublishApproval'"
          type="primary"
          :icon="Promotion"
          :loading="publishing"
          :disabled="saving || validating || Boolean(lifecycleAction)"
          @click="publish"
        >
          {{
            canAiResourceRepublish(
              skill.displayPublishStatus,
              skill.publishStatus,
            )
              ? '重新发布'
              : '发布'
          }}
        </ElButton>
        <ElDropdown
          v-if="canManage || canSubmitOffline || canSubmitDelete"
          :disabled="operationLocked"
          trigger="click"
          @command="handleLifecycleCommand"
        >
          <ElButton
            :icon="MoreFilled"
            :disabled="operationLocked"
            aria-label="更多操作"
          />
          <template #dropdown>
            <ElDropdownMenu>
              <ElDropdownItem v-if="canManage" command="settings" :icon="Edit">
                编辑基本信息
              </ElDropdownItem>
              <ElDropdownItem
                v-if="canSubmitOffline"
                command="offline"
                :icon="Upload"
                :divided="canManage"
              >
                下线
              </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 ? '无权查看 Skill' : 'Skill 加载失败'"
        :sub-title="loadError"
      >
        <template #extra>
          <ElButton
            :type="loadAccessDenied ? 'primary' : 'default'"
            @click="backToSkillList"
          >
            返回 Skill 列表
          </ElButton>
          <ElButton v-if="!loadAccessDenied" type="primary" @click="init">
            重新加载
          </ElButton>
        </template>
      </ElResult>
      <div v-else class="skill-detail-page__section-host">
        <SkillResourceWorkbench
          id="skill-panel-resources"
          ref="resourceWorkbenchRef"
          v-model:active-panel="activeSection"
          :capability-available="!isNew && canViewCapabilities"
          :capability-count="Number(skill.capabilityCount || 0)"
          :skill-id="skillId"
          :initial-content="String(skill.skillContent || '')"
          :readonly="!canEditFiles || operationLocked"
          @dirty="resourceDirty = $event"
          @locate-capability="locateCapabilitySection"
          @request-save="saveFiles()"
        >
          <template #capability>
            <SkillCapabilityPanel
              v-if="!isNew && canViewCapabilities"
              ref="capabilityPanelRef"
              embedded
              :capability-hash="String(skill.capabilityHash || '')"
              :skill-id="skillId"
              :locked="operationLocked"
              :readonly="!canBindCapabilities"
              @dirty="capabilityDirty = $event"
              @saved="handleCapabilitySaved"
            />
          </template>
        </SkillResourceWorkbench>
      </div>
    </main>

    <SkillSettingsDialog
      v-if="!isNew && canManage"
      v-model="settingsDialogOpen"
      :categories="categories"
      :categories-loading="categoryLoading"
      :category-load-error="categoryLoadError"
      :skill="skill"
      @retry-categories="loadCategories()"
      @saved="handleSettingsSaved"
    />
  </div>
</template>

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

.skill-detail-page__header {
  display: flex;
  gap: var(--space-6);
  align-items: center;
  justify-content: space-between;
}

.skill-detail-page__title-group,
.skill-detail-page__actions,
.skill-detail-page__title > div {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.skill-detail-page__title-group {
  align-items: center;
  min-width: 0;
}

.skill-detail-page__title {
  min-width: 0;
}

.skill-detail-page__title h1 {
  margin: 0;
}

.skill-detail-page__title h1 {
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 22px;
  font-weight: 650;
  color: hsl(var(--text-strong));
  letter-spacing: -0.015em;
  white-space: nowrap;
}

.skill-detail-page__save-state {
  min-width: 72px;
  font-size: 12px;
  color: hsl(var(--text-muted));
  text-align: right;
}

.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__section-host {
  display: flex;
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

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

@media (max-width: 800px) {
  .skill-detail-page {
    padding: var(--space-4);
  }

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

  .skill-detail-page__actions {
    justify-content: flex-end;
  }
}
</style>
