<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { systemAPI, type SkillUploadFileInput } from "@/api";
import ApiErrorDiagnostic from "@/components/common/ApiErrorDiagnostic.vue";
import BaseDialog from "@/components/common/BaseDialog.vue";
import DataTable from "@/components/common/DataTable.vue";
import type { DataTableColumn } from "@/components/common/DataTable.vue";
import EmptyState from "@/components/common/EmptyState.vue";
import LoadingState from "@/components/common/LoadingState.vue";
import PageHeader from "@/components/common/PageHeader.vue";
import Pagination from "@/components/common/Pagination.vue";
import TablePageLayout from "@/components/layout/TablePageLayout.vue";
import { useForm } from "@/composables/useForm";
import { useTableLoader } from "@/composables/useTableLoader";
import type { ApiError, ErrorDiagnostic, SkillFilePayload, SkillRecord, SkillTreeEntry, SkillsResponse } from "@/types";

const columns: DataTableColumn[] = [
  { key: "name", label: "Skill" },
  { key: "source", label: "来源" },
  { key: "version", label: "版本" },
  { key: "files", label: "文件", align: "right" },
  { key: "assembly", label: "AgentRun assembly" },
  { key: "actions", label: "操作", align: "right" }
];

const payload = ref<SkillsResponse | null>(null);
const selectedSkill = ref<SkillRecord | null>(null);
const tree = ref<SkillTreeEntry[]>([]);
const treeError = ref<string | null>(null);
const treeApiError = ref<ApiError | null>(null);
const treeDiagnostic = ref<ErrorDiagnostic | null>(null);
const filePreview = ref<SkillFilePayload | null>(null);
const fileError = ref<string | null>(null);
const fileApiError = ref<ApiError | null>(null);
const fileDiagnostic = ref<ErrorDiagnostic | null>(null);
const uploadOpen = ref(false);
const uploadName = ref("");
const uploadFiles = ref<SkillUploadFileInput[]>([]);
const uploadForm = useForm();
const page = ref(1);
const pageSize = 10;

const table = useTableLoader<SkillRecord>(async () => {
  const response = await systemAPI.skills();
  if (!response.ok) throw response;
  payload.value = response.data;
  return response.data?.skills ?? [];
});

const rows = computed(() => table.rows.value);
const pageRows = computed(() => rows.value.slice((page.value - 1) * pageSize, page.value * pageSize));
const uploadedCount = computed(() => rows.value.filter((row) => row.source === "uploaded").length);
const preinstalledCount = computed(() => rows.value.filter((row) => row.source === "preinstalled").length);
const assembly = computed(() => payload.value?.agentRunAssembly ?? {});
const assemblyResourceBundle = computed(() => (assembly.value.resourceBundle && typeof assembly.value.resourceBundle === "object" ? assembly.value.resourceBundle as Record<string, unknown> : {}));
const assemblyPrompt = computed(() => (assembly.value.promptAssembly && typeof assembly.value.promptAssembly === "object" ? assembly.value.promptAssembly as Record<string, unknown> : {}));

onMounted(() => void table.reload());

function rowKey(row: SkillRecord): string {
  return row.id;
}

async function inspectSkill(row: SkillRecord): Promise<void> {
  selectedSkill.value = row;
  tree.value = [];
  filePreview.value = null;
  treeError.value = null;
  treeApiError.value = null;
  treeDiagnostic.value = null;
  fileError.value = null;
  fileApiError.value = null;
  fileDiagnostic.value = null;
  const treeResponse = await systemAPI.skillTree(row.id);
  if (!treeResponse.ok) {
    treeError.value = treeResponse.error ?? `HTTP ${treeResponse.status}`;
    treeApiError.value = treeResponse.apiError ?? null;
    treeDiagnostic.value = treeResponse.diagnostic ?? null;
    return;
  }
  tree.value = treeResponse.data?.tree ?? [];
  await previewFile(row, "SKILL.md");
}

async function previewFile(row: SkillRecord, path = "SKILL.md"): Promise<void> {
  fileError.value = null;
  fileApiError.value = null;
  fileDiagnostic.value = null;
  const response = await systemAPI.skillFile(row.id, path);
  if (!response.ok) {
    filePreview.value = null;
    fileError.value = response.error ?? `HTTP ${response.status}`;
    fileApiError.value = response.apiError ?? null;
    fileDiagnostic.value = response.diagnostic ?? null;
    return;
  }
  filePreview.value = response.data?.file ?? { path, content: response.data?.content ?? "" };
}

async function onUploadInput(event: Event): Promise<void> {
  const input = event.target as HTMLInputElement;
  const files = Array.from(input.files ?? []);
  uploadFiles.value = await Promise.all(files.slice(0, 80).map(async (file) => ({
    relativePath: file.webkitRelativePath || file.name,
    contentText: await file.text()
  })));
}

async function uploadSkill(): Promise<void> {
  const ok = await uploadForm.submit(async () => {
    const response = await systemAPI.uploadSkill(uploadName.value, uploadFiles.value);
    if (!response.ok) throw response;
    uploadOpen.value = false;
    uploadName.value = "";
    uploadFiles.value = [];
    await table.reload();
    const uploaded = response.data?.skill;
    if (uploaded) await inspectSkill(uploaded);
  }, "Skill uploaded");
  if (!ok) return;
}

function displayDate(value?: string | null): string {
  if (!value) return "-";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  return date.toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
}

function formatBytes(value?: number): string {
  const bytes = Number(value);
  if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
</script>

<template>
  <section class="route-stack system-page skills-page">
    <PageHeader eyebrow="System" title="Skills" description="技能包管理直接使用 /v1/skills、upload、tree 和 file preview 接口，展示 AgentRun assembly 状态。" />
    <LoadingState v-if="table.loading.value && !rows.length" />
    <section v-else-if="table.error.value" class="data-panel">
      <EmptyState title="Skills 加载失败" :description="table.error.value" :api-error="table.apiError.value" :diagnostic="table.diagnostic.value" action-label="重试" @action="table.reload" />
    </section>
    <template v-else>
      <section class="system-summary-grid" aria-label="Skills summary">
        <article class="metric-card"><span>Total</span><strong>{{ rows.length }}</strong><small>{{ preinstalledCount }} preinstalled / {{ uploadedCount }} uploaded</small></article>
        <article class="metric-card"><span>ResourceBundle</span><strong>{{ assemblyResourceBundle.kind || '-' }}</strong><small>{{ assemblyResourceBundle.skillsDir || '-' }}</small></article>
        <article class="metric-card"><span>Prompt</span><strong>{{ assemblyPrompt.status || '-' }}</strong><small>valuesPrinted={{ assemblyPrompt.valuesPrinted === false ? 'false' : 'unknown' }}</small></article>
        <article class="metric-card"><span>Roots</span><strong>{{ payload?.roots?.uploaded ? 'ready' : 'unknown' }}</strong><small>{{ payload?.roots?.codexAggregation || '-' }}</small></article>
      </section>
      <TablePageLayout title="Skill registry" subtitle="preinstalled + uploaded roots / tree / SKILL.md preview">
        <template #actions>
          <button class="table-action" type="button" :disabled="table.loading.value" @click="table.reload">刷新</button>
          <button class="table-action" type="button" @click="uploadOpen = true">Upload</button>
        </template>
        <DataTable :columns="columns" :rows="pageRows" :row-key="rowKey" empty-text="暂无可用 skill。">
          <template #cell-name="{ row }">
            <strong>{{ (row as SkillRecord).name || (row as SkillRecord).id }}</strong>
            <small>{{ (row as SkillRecord).description || '-' }}</small>
          </template>
          <template #cell-source="{ row }"><span class="status-pill" :data-status="(row as SkillRecord).source || 'pending'">{{ (row as SkillRecord).source || '-' }}</span></template>
          <template #cell-version="{ row }"><code>{{ (row as SkillRecord).version || (row as SkillRecord).commitId || '-' }}</code><small>{{ displayDate((row as SkillRecord).updatedAt || (row as SkillRecord).createdAt) }}</small></template>
          <template #cell-files="{ row }"><strong>{{ (row as SkillRecord).fileCount ?? 0 }}</strong><small>{{ formatBytes((row as SkillRecord).sizeBytes) }}</small></template>
          <template #cell-assembly="{ row }"><code>{{ (row as SkillRecord).manifestPath || '-' }}</code></template>
          <template #cell-actions="{ row }"><button class="table-action" type="button" @click="inspectSkill(row as SkillRecord)">Tree</button></template>
        </DataTable>
        <template #pagination><Pagination v-model:page="page" :page-size="pageSize" :total="rows.length" /></template>
      </TablePageLayout>

      <section class="system-split-grid skills-detail-grid">
        <section class="data-panel skills-tree-panel">
          <header class="panel-header"><h2>目录树</h2><span class="status-pill" :data-status="treeError ? 'error' : selectedSkill ? 'ready' : 'pending'">{{ selectedSkill?.name || '未选择' }}</span></header>
          <ApiErrorDiagnostic v-if="treeError" :error="treeError" :api-error="treeApiError" :diagnostic="treeDiagnostic" compact />
          <p v-else-if="!selectedSkill" class="muted-box">选择一行 skill 查看 tree 和 SKILL.md。</p>
          <ul v-else class="skill-tree-list">
            <li v-for="entry in tree" :key="entry.path" :data-type="entry.type">
              <button type="button" :disabled="entry.type !== 'file'" @click="selectedSkill && previewFile(selectedSkill, entry.path)">{{ entry.type === 'directory' ? 'dir' : 'file' }}</button>
              <span>{{ entry.path }}</span>
              <small>{{ entry.type === 'file' ? formatBytes(entry.sizeBytes ?? 0) : '' }}</small>
            </li>
          </ul>
        </section>
        <section class="data-panel skills-preview-panel">
          <header class="panel-header"><h2>文件预览</h2><code>{{ filePreview?.path || 'SKILL.md' }}</code></header>
          <ApiErrorDiagnostic v-if="fileError" :error="fileError" :api-error="fileApiError" :diagnostic="fileDiagnostic" compact />
          <pre v-else class="mini-log">{{ filePreview?.content || '等待选择 skill 或文件。' }}</pre>
        </section>
      </section>
    </template>

    <BaseDialog :open="uploadOpen" title="Upload skill" description="上传文本型 skill 目录；至少包含 SKILL.md。" wide @close="uploadOpen = false">
      <form class="admin-form" @submit.prevent="uploadSkill">
        <label><span>名称</span><input v-model="uploadName" type="text" placeholder="可选；默认读取 SKILL.md frontmatter"></label>
        <label><span>目录或文件</span><input type="file" multiple webkitdirectory @change="onUploadInput"></label>
        <p class="muted-box">已选择 {{ uploadFiles.length }} 个文本文件；后端会剥离单层顶级目录并写入 uploaded skill root。</p>
        <ApiErrorDiagnostic v-if="uploadForm.error.value" :error="uploadForm.error.value" :api-error="uploadForm.apiError.value" :diagnostic="uploadForm.diagnostic.value" compact />
        <p v-if="uploadForm.notice.value" class="form-notice">{{ uploadForm.notice.value }}</p>
      </form>
      <template #footer>
        <button class="table-action" type="button" :disabled="uploadForm.loading.value" @click="uploadOpen = false">取消</button>
        <button class="table-action" type="button" :disabled="uploadForm.loading.value || uploadFiles.length === 0" @click="uploadSkill">{{ uploadForm.loading.value ? '上传中' : '上传' }}</button>
      </template>
    </BaseDialog>
  </section>
</template>
