<script lang="ts" setup>
import type { Dayjs } from 'dayjs';

import type { EchartsUIType } from '@vben/plugins/echarts';

import type { OpenApiCenterApi } from '#/api/manager/open-api';

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

import { Page } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';

import {
  Button,
  Card,
  DatePicker,
  Form,
  FormItem,
  Input,
  message,
  Modal,
  Select,
  Switch,
  Table,
  Tabs,
  Tag,
  Tooltip,
} from 'ant-design-vue';
import dayjs from 'dayjs';

import {
  createOpenApiEndpoint,
  getOpenApiConsumers,
  getOpenApiEndpoints,
  getOpenApiOverview,
  getOpenApiUsage,
  updateOpenApiConsumer,
  updateOpenApiEndpoint,
} from '#/api/manager/open-api';

const RangePicker = DatePicker.RangePicker;
const activeTab = ref('overview');
const loading = ref(false);
const dateRange = ref<[Dayjs, Dayjs]>([
  dayjs().subtract(6, 'day').startOf('day'),
  dayjs().endOf('day'),
]);
const serviceCode = ref<string>();
const category = ref<string>();
const callerType = ref<string>();
const selectedEndpointId = ref<string>();

const overview = ref<OpenApiCenterApi.Overview>({
  activeConsumers: 0,
  activeIps: 0,
  anonymousCalls: 0,
  authenticatedCalls: 0,
  categoryDistribution: [],
  successRate: 100,
  topConsumers: [],
  topEndpoints: [],
  totalCalls: 0,
  trend: [],
  unauthorizedCalls: 0,
});
const endpoints = ref<OpenApiCenterApi.Endpoint[]>([]);
const consumers = ref<OpenApiCenterApi.Consumer[]>([]);
const usage = ref<OpenApiCenterApi.Usage[]>([]);

const trendChartRef = ref<EchartsUIType>();
const distributionChartRef = ref<EchartsUIType>();
const rankingChartRef = ref<EchartsUIType>();
const { renderEcharts: renderTrend } = useEcharts(trendChartRef);
const { renderEcharts: renderDistribution } = useEcharts(distributionChartRef);
const { renderEcharts: renderRanking } = useEcharts(rankingChartRef);

const serviceOptions = [
  { label: '票通平台', value: 'invoice' },
  { label: 'AI 后端', value: 'ai-fastapi' },
  { label: '核心 Ktor', value: 'ktor-core' },
  { label: 'F10 公开页', value: 'f10' },
];
const categoryOptions = computed(() =>
  [...new Set(endpoints.value.map((item) => item.category))]
    .filter(Boolean)
    .map((value) => ({ label: value, value })),
);
const endpointOptions = computed(() =>
  endpoints.value.map((item) => ({
    label: `${item.name} · ${item.httpMethod}`,
    value: item.id,
  })),
);
const metricCards = computed(() => [
  {
    accent: '#3b82f6',
    icon: 'lucide:activity',
    label: '总调用量',
    note: '所选周期内的网关请求',
    value: overview.value.totalCalls,
  },
  {
    accent: '#0f9f7f',
    icon: 'lucide:badge-check',
    label: '认证调用',
    note: `${overview.value.activeConsumers} 个活跃调用方`,
    value: overview.value.authenticatedCalls,
  },
  {
    accent: '#f59e0b',
    icon: 'lucide:scan-face',
    label: '匿名调用',
    note: `${overview.value.activeIps} 个来源 IP`,
    value: overview.value.anonymousCalls,
  },
  {
    accent: '#ef4444',
    icon: 'lucide:shield-alert',
    label: '认证失败',
    note: 'HTTP 401 / 403',
    value: overview.value.unauthorizedCalls,
  },
  {
    accent: '#8b5cf6',
    icon: 'lucide:gauge',
    label: '请求成功率',
    note: 'HTTP 2xx / 3xx',
    suffix: '%',
    value: overview.value.successRate,
  },
]);

const endpointColumns = [
  { key: 'name', title: '接口资产', width: 250 },
  { key: 'service', title: '所属服务', width: 130 },
  { key: 'path', title: '请求路径', width: 410 },
  { dataIndex: 'recentCalls', key: 'recentCalls', title: '近 24h', width: 100 },
  {
    dataIndex: 'recentConsumers',
    key: 'recentConsumers',
    title: '调用方',
    width: 90,
  },
  { key: 'lastCalledAt', title: '最后调用', width: 165 },
  { key: 'status', title: '状态', width: 100 },
  { key: 'action', title: '', width: 76 },
];
const consumerColumns = [
  { key: 'identity', title: '调用方', width: 260 },
  { dataIndex: 'customId', key: 'customId', title: 'Custom ID', width: 180 },
  { dataIndex: 'callCount', key: 'callCount', title: '累计调用', width: 120 },
  {
    dataIndex: 'endpointCount',
    key: 'endpointCount',
    title: '接口数',
    width: 90,
  },
  { dataIndex: 'recentIp', key: 'recentIp', title: '最近 IP', width: 150 },
  { key: 'lastSeenAt', title: '最后调用', width: 170 },
  { key: 'status', title: '状态', width: 100 },
  { key: 'action', title: '', width: 76 },
];
const usageColumns = [
  { key: 'bucket', title: '时间', width: 165 },
  { key: 'endpoint', title: '接口', width: 230 },
  { key: 'caller', title: '调用方', width: 210 },
  { dataIndex: 'clientIp', key: 'clientIp', title: '来源 IP', width: 150 },
  { dataIndex: 'callCount', key: 'callCount', title: '调用量', width: 100 },
  { dataIndex: 'successCount', key: 'successCount', title: '成功', width: 90 },
  { dataIndex: 'failureCount', key: 'failureCount', title: '失败', width: 90 },
  {
    dataIndex: 'unauthorizedCount',
    key: 'unauthorizedCount',
    title: '认证失败',
    width: 100,
  },
];

const endpointModalOpen = ref(false);
const endpointSaving = ref(false);
const editingEndpointId = ref<string>();
const endpointForm = reactive<OpenApiCenterApi.SaveEndpoint>({
  category: '',
  description: '',
  httpMethod: 'POST',
  kongRouteId: '',
  kongServiceId: '',
  monitorEnabled: true,
  name: '',
  owner: '',
  pathTemplate: '',
  serviceCode: 'invoice',
  status: 'enabled',
});

const consumerModalOpen = ref(false);
const consumerSaving = ref(false);
const editingConsumer = ref<OpenApiCenterApi.Consumer>();
const consumerForm = reactive({
  description: '',
  displayName: '',
  status: 'active',
});

function buildQuery(): OpenApiCenterApi.Query {
  return {
    callerType: callerType.value,
    category: category.value,
    endpointId: selectedEndpointId.value,
    from: dateRange.value[0].toISOString(),
    serviceCode: serviceCode.value,
    to: dateRange.value[1].toISOString(),
  };
}

async function refreshAll() {
  loading.value = true;
  try {
    const query = buildQuery();
    const [overviewData, endpointData, consumerData, usageData] =
      await Promise.all([
        getOpenApiOverview(query),
        getOpenApiEndpoints(),
        getOpenApiConsumers(),
        getOpenApiUsage(query),
      ]);
    overview.value = overviewData;
    endpoints.value = endpointData;
    consumers.value = consumerData;
    usage.value = usageData;
    await nextTick();
    renderCharts();
  } finally {
    loading.value = false;
  }
}

function renderCharts() {
  const trend = overview.value.trend;
  renderTrend({
    animationDuration: 650,
    color: ['#2563eb', '#10b981', '#f59e0b', '#ef4444'],
    grid: { bottom: 20, containLabel: true, left: 8, right: 14, top: 52 },
    legend: {
      icon: 'roundRect',
      itemHeight: 7,
      itemWidth: 18,
      right: 4,
      textStyle: { color: '#8492a6' },
      top: 4,
    },
    series: [
      {
        areaStyle: {
          color: {
            colorStops: [
              { color: 'rgba(37,99,235,.25)', offset: 0 },
              { color: 'rgba(37,99,235,0)', offset: 1 },
            ],
            type: 'linear',
            x: 0,
            x2: 0,
            y: 0,
            y2: 1,
          },
        },
        data: trend.map((item) => item.calls),
        emphasis: { focus: 'series' },
        name: '总调用',
        showSymbol: false,
        smooth: 0.35,
        type: 'line',
      },
      {
        data: trend.map((item) => item.authenticatedCalls),
        name: '认证调用',
        showSymbol: false,
        smooth: 0.35,
        type: 'line',
      },
      {
        data: trend.map((item) => item.anonymousCalls),
        name: '匿名调用',
        showSymbol: false,
        smooth: 0.35,
        type: 'line',
      },
      {
        data: trend.map((item) => item.failures),
        name: '失败',
        showSymbol: false,
        smooth: 0.35,
        type: 'line',
      },
    ],
    tooltip: {
      axisPointer: { type: 'line' },
      backgroundColor: 'rgba(15,23,42,.94)',
      borderWidth: 0,
      textStyle: { color: '#fff' },
      trigger: 'axis',
    },
    xAxis: {
      axisLabel: {
        color: '#8492a6',
        formatter: (value: string) =>
          dayjs(value).format(trend.length > 24 ? 'MM-DD' : 'MM-DD HH:mm'),
        hideOverlap: true,
      },
      axisLine: { lineStyle: { color: 'rgba(148,163,184,.2)' } },
      axisTick: { show: false },
      boundaryGap: false,
      data: trend.map((item) => item.bucket),
      type: 'category',
    },
    yAxis: {
      axisLabel: { color: '#8492a6' },
      splitLine: { lineStyle: { color: 'rgba(148,163,184,.12)' } },
      type: 'value',
    },
  });

  renderDistribution({
    color: ['#2563eb', '#10b981', '#8b5cf6', '#f59e0b', '#06b6d4', '#64748b'],
    legend: {
      bottom: 0,
      icon: 'circle',
      itemHeight: 8,
      itemWidth: 8,
      textStyle: { color: '#8492a6' },
    },
    series: [
      {
        center: ['50%', '43%'],
        data: overview.value.categoryDistribution.map((item) => ({
          name: item.name,
          value: item.value,
        })),
        itemStyle: { borderColor: 'rgba(255,255,255,.75)', borderWidth: 2 },
        label: { show: false },
        radius: ['52%', '76%'],
        type: 'pie',
      },
    ],
    tooltip: {
      backgroundColor: 'rgba(15,23,42,.94)',
      borderWidth: 0,
      textStyle: { color: '#fff' },
      trigger: 'item',
    },
  });

  const ranking = [...overview.value.topEndpoints].reverse();
  renderRanking({
    color: ['#2563eb'],
    grid: { bottom: 8, containLabel: true, left: 8, right: 26, top: 8 },
    series: [
      {
        barMaxWidth: 14,
        data: ranking.map((item) => ({
          itemStyle: {
            borderRadius: [0, 8, 8, 0],
            color: {
              colorStops: [
                { color: '#60a5fa', offset: 0 },
                { color: '#2563eb', offset: 1 },
              ],
              type: 'linear',
              x: 0,
              x2: 1,
              y: 0,
              y2: 0,
            },
          },
          value: item.value,
        })),
        label: { color: '#64748b', position: 'right', show: true },
        type: 'bar',
      },
    ],
    tooltip: { trigger: 'axis' },
    xAxis: { show: false, type: 'value' },
    yAxis: {
      axisLabel: {
        color: '#64748b',
        formatter: (value: string) =>
          value.length > 10 ? `${value.slice(0, 10)}…` : value,
      },
      axisLine: { show: false },
      axisTick: { show: false },
      data: ranking.map((item) => item.name),
      type: 'category',
    },
  });
}

function resetFilters() {
  dateRange.value = [
    dayjs().subtract(6, 'day').startOf('day'),
    dayjs().endOf('day'),
  ];
  serviceCode.value = undefined;
  category.value = undefined;
  callerType.value = undefined;
  selectedEndpointId.value = undefined;
  refreshAll();
}

function openEndpointModal(row?: OpenApiCenterApi.Endpoint) {
  editingEndpointId.value = row?.id;
  Object.assign(endpointForm, {
    category: row?.category ?? '',
    description: row?.description ?? '',
    httpMethod: row?.httpMethod ?? 'POST',
    kongRouteId: row?.kongRouteId ?? '',
    kongServiceId: row?.kongServiceId ?? '',
    monitorEnabled: row?.monitorEnabled ?? true,
    name: row?.name ?? '',
    owner: row?.owner ?? '',
    pathTemplate: row?.pathTemplate ?? '',
    serviceCode: row?.serviceCode ?? 'invoice',
    status: row?.status ?? 'enabled',
  });
  endpointModalOpen.value = true;
}

function editEndpointRecord(row: Record<string, unknown>) {
  openEndpointModal(row as unknown as OpenApiCenterApi.Endpoint);
}

async function saveEndpoint() {
  if (
    !endpointForm.name.trim() ||
    !endpointForm.category.trim() ||
    !endpointForm.pathTemplate.trim()
  ) {
    message.warning('请完整填写接口名称、分类和请求路径');
    return;
  }
  endpointSaving.value = true;
  try {
    await (editingEndpointId.value
      ? updateOpenApiEndpoint(editingEndpointId.value, { ...endpointForm })
      : createOpenApiEndpoint({ ...endpointForm }));
    message.success(
      editingEndpointId.value ? '接口资产已更新' : '接口资产已创建',
    );
    endpointModalOpen.value = false;
    await refreshAll();
  } finally {
    endpointSaving.value = false;
  }
}

function openConsumerModal(row: OpenApiCenterApi.Consumer) {
  editingConsumer.value = row;
  Object.assign(consumerForm, {
    description: row.description,
    displayName: row.displayName || row.username,
    status: row.status,
  });
  consumerModalOpen.value = true;
}

function editConsumerRecord(row: Record<string, unknown>) {
  openConsumerModal(row as unknown as OpenApiCenterApi.Consumer);
}

async function saveConsumer() {
  if (!editingConsumer.value) return;
  consumerSaving.value = true;
  try {
    await updateOpenApiConsumer(editingConsumer.value.id, { ...consumerForm });
    message.success('调用方资料已更新');
    consumerModalOpen.value = false;
    await refreshAll();
  } finally {
    consumerSaving.value = false;
  }
}

function formatNumber(value: number) {
  return new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 2 }).format(
    value || 0,
  );
}

function formatTime(value?: string) {
  return value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '尚无调用';
}

function serviceName(code: string) {
  return serviceOptions.find((item) => item.value === code)?.label ?? code;
}

function methodColor(method: string) {
  return (
    {
      DELETE: 'red',
      GET: 'blue',
      PATCH: 'orange',
      POST: 'green',
      PUT: 'purple',
    }[method] ?? 'default'
  );
}

function usageRowKey(record: OpenApiCenterApi.Usage) {
  return [
    record.bucket,
    record.endpointId,
    record.callerType,
    record.consumerId || record.clientIp,
  ].join('-');
}

watch([trendChartRef, distributionChartRef, rankingChartRef], () => {
  if (overview.value) renderCharts();
});

onMounted(refreshAll);
</script>

<template>
  <Page auto-content-height class="open-api-page">
    <div class="hero-panel">
      <div class="hero-orb hero-orb-one"></div>
      <div class="hero-orb hero-orb-two"></div>
      <div class="hero-content">
        <div>
          <div class="hero-kicker">
            <span class="live-dot"></span>
            API OPERATIONS CENTER
          </div>
          <h1>开放接口中心</h1>
          <p>统一洞察票通、人工智能与业务开放能力，让每一次调用都清晰可见。</p>
        </div>
        <div class="hero-actions">
          <div class="health-pill">
            <IconifyIcon icon="lucide:shield-check" />
            <span>Kong 统一采集</span>
          </div>
          <Button ghost size="large" @click="refreshAll">
            <template #icon>
              <IconifyIcon
                :class="{ spinning: loading }"
                icon="lucide:refresh-cw"
              />
            </template>
            刷新数据
          </Button>
        </div>
      </div>
    </div>

    <Card :bordered="false" class="filter-card">
      <div class="filter-bar">
        <div class="filter-title">
          <IconifyIcon icon="lucide:sliders-horizontal" />
          <span>统计范围</span>
        </div>
        <RangePicker
          v-model:value="dateRange"
          :allow-clear="false"
          class="range-picker"
          show-time
        />
        <Select
          v-model:value="serviceCode"
          allow-clear
          class="filter-select"
          :options="serviceOptions"
          placeholder="全部服务"
        />
        <Select
          v-model:value="category"
          allow-clear
          class="filter-select"
          :options="categoryOptions"
          placeholder="全部分类"
        />
        <Select
          v-model:value="callerType"
          allow-clear
          class="filter-select"
          :options="[
            { label: '已认证调用', value: 'consumer' },
            { label: '匿名调用', value: 'anonymous' },
          ]"
          placeholder="全部调用方"
        />
        <Button type="primary" @click="refreshAll">
          <template #icon><IconifyIcon icon="lucide:search" /></template>
          应用筛选
        </Button>
        <Button type="text" @click="resetFilters">重置</Button>
      </div>
    </Card>

    <div class="metric-grid">
      <Card
        v-for="item in metricCards"
        :key="item.label"
        :bordered="false"
        class="metric-card"
      >
        <div class="metric-head">
          <div
            class="metric-icon"
            :style="{ background: `${item.accent}16`, color: item.accent }"
          >
            <IconifyIcon :icon="item.icon" />
          </div>
          <span class="metric-label">{{ item.label }}</span>
        </div>
        <div class="metric-value">
          {{ formatNumber(item.value) }}<small>{{ item.suffix }}</small>
        </div>
        <div class="metric-note">{{ item.note }}</div>
        <div class="metric-line" :style="{ background: item.accent }"></div>
      </Card>
    </div>

    <Card :bordered="false" class="content-card">
      <Tabs v-model:active-key="activeTab" class="center-tabs">
        <Tabs.TabPane key="overview">
          <template #tab>
            <span class="tab-label"
              ><IconifyIcon
                icon="lucide:chart-no-axes-combined"
              />运行总览</span
            >
          </template>
          <div class="overview-grid">
            <section class="chart-panel chart-panel-wide">
              <div class="panel-heading">
                <div>
                  <span class="panel-kicker">CALL VOLUME</span>
                  <h3>调用趋势</h3>
                </div>
                <Tag color="blue">成功率 {{ overview.successRate }}%</Tag>
              </div>
              <EchartsUI ref="trendChartRef" class="trend-chart" />
            </section>
            <section class="chart-panel">
              <div class="panel-heading">
                <div>
                  <span class="panel-kicker">PORTFOLIO</span>
                  <h3>能力分布</h3>
                </div>
              </div>
              <EchartsUI ref="distributionChartRef" class="side-chart" />
            </section>
            <section class="chart-panel">
              <div class="panel-heading">
                <div>
                  <span class="panel-kicker">TOP ENDPOINTS</span>
                  <h3>热门接口</h3>
                </div>
              </div>
              <EchartsUI ref="rankingChartRef" class="side-chart" />
            </section>
            <section class="chart-panel">
              <div class="panel-heading">
                <div>
                  <span class="panel-kicker">TOP CALLERS</span>
                  <h3>活跃调用方</h3>
                </div>
              </div>
              <div class="caller-ranking">
                <div
                  v-for="(item, index) in overview.topConsumers"
                  :key="item.key"
                  class="caller-row"
                >
                  <span class="rank-index" :class="{ podium: index < 3 }">{{
                    index + 1
                  }}</span>
                  <div class="caller-avatar">
                    <IconifyIcon
                      :icon="
                        item.key.startsWith('ip:')
                          ? 'lucide:globe-2'
                          : 'lucide:building-2'
                      "
                    />
                  </div>
                  <div class="caller-copy">
                    <strong>{{ item.name }}</strong>
                    <span>{{ item.secondary || '开放接口调用方' }}</span>
                  </div>
                  <b>{{ formatNumber(item.value) }}</b>
                </div>
                <div
                  v-if="overview.topConsumers.length === 0"
                  class="empty-state"
                >
                  <IconifyIcon icon="lucide:radio-tower" />
                  <span>等待 Kong 推送首批调用数据</span>
                </div>
              </div>
            </section>
          </div>
        </Tabs.TabPane>

        <Tabs.TabPane key="endpoints">
          <template #tab>
            <span class="tab-label"
              ><IconifyIcon icon="lucide:boxes" />接口资产</span
            >
          </template>
          <div class="table-toolbar">
            <div>
              <h3>接口资产目录</h3>
              <p>维护路径模板、业务分类与 Kong 路由映射。</p>
            </div>
            <Button type="primary" @click="openEndpointModal()">
              <template #icon><IconifyIcon icon="lucide:plus" /></template>
              新增接口
            </Button>
          </div>
          <Table
            :columns="endpointColumns"
            :data-source="endpoints"
            :loading="loading"
            :pagination="{ pageSize: 10, showSizeChanger: true }"
            :scroll="{ x: 1400 }"
            row-key="id"
          >
            <template #bodyCell="{ column, record }">
              <template v-if="column.key === 'name'">
                <div class="asset-name">
                  <div class="asset-icon">
                    <IconifyIcon icon="lucide:webhook" />
                  </div>
                  <div>
                    <strong>{{ record.name }}</strong>
                    <span>{{ record.category }}</span>
                  </div>
                </div>
              </template>
              <template v-else-if="column.key === 'service'">
                <Tag>{{ serviceName(record.serviceCode) }}</Tag>
              </template>
              <template v-else-if="column.key === 'path'">
                <div class="path-cell">
                  <Tag :color="methodColor(record.httpMethod)">
                    {{ record.httpMethod }}
                  </Tag>
                  <Tooltip :title="record.pathTemplate">
                    <code>{{ record.pathTemplate }}</code>
                  </Tooltip>
                </div>
              </template>
              <template v-else-if="column.key === 'lastCalledAt'">
                <span class="muted-text">{{
                  formatTime(record.lastCalledAt)
                }}</span>
              </template>
              <template v-else-if="column.key === 'status'">
                <Tag
                  :color="
                    record.monitorEnabled && record.status === 'enabled'
                      ? 'success'
                      : 'default'
                  "
                >
                  {{
                    record.monitorEnabled && record.status === 'enabled'
                      ? '监控中'
                      : '已停用'
                  }}
                </Tag>
              </template>
              <template v-else-if="column.key === 'action'">
                <Button
                  size="small"
                  type="link"
                  @click="editEndpointRecord(record)"
                >
                  编辑
                </Button>
              </template>
            </template>
          </Table>
        </Tabs.TabPane>

        <Tabs.TabPane key="consumers">
          <template #tab>
            <span class="tab-label"
              ><IconifyIcon icon="lucide:users-round" />调用方</span
            >
          </template>
          <div class="table-toolbar">
            <div>
              <h3>调用方识别</h3>
              <p>来源于 Kong Consumer；匿名请求按来源 IP 聚合。</p>
            </div>
            <Tag color="cyan">{{ consumers.length }} 个已识别 Consumer</Tag>
          </div>
          <Table
            :columns="consumerColumns"
            :data-source="consumers"
            :loading="loading"
            :pagination="{ pageSize: 10 }"
            :scroll="{ x: 1250 }"
            row-key="id"
          >
            <template #bodyCell="{ column, record }">
              <template v-if="column.key === 'identity'">
                <div class="asset-name">
                  <div class="asset-icon consumer-icon">
                    <IconifyIcon icon="lucide:building-2" />
                  </div>
                  <div>
                    <strong>{{
                      record.displayName || record.username || '未命名调用方'
                    }}</strong>
                    <span>{{ record.username }}</span>
                  </div>
                </div>
              </template>
              <template v-else-if="column.key === 'lastSeenAt'">
                <span class="muted-text">{{
                  formatTime(record.lastSeenAt)
                }}</span>
              </template>
              <template v-else-if="column.key === 'status'">
                <Tag
                  :color="record.status === 'active' ? 'success' : 'default'"
                >
                  {{ record.status === 'active' ? '活跃' : '停用' }}
                </Tag>
              </template>
              <template v-else-if="column.key === 'action'">
                <Button
                  size="small"
                  type="link"
                  @click="editConsumerRecord(record)"
                >
                  编辑
                </Button>
              </template>
            </template>
          </Table>
        </Tabs.TabPane>

        <Tabs.TabPane key="usage">
          <template #tab>
            <span class="tab-label"
              ><IconifyIcon icon="lucide:table-properties" />使用统计</span
            >
          </template>
          <div class="table-toolbar usage-toolbar">
            <div>
              <h3>小时聚合数据</h3>
              <p>仅保留调用方、来源 IP、时间与次数，不保存请求内容。</p>
            </div>
            <Select
              v-model:value="selectedEndpointId"
              allow-clear
              class="endpoint-filter"
              :options="endpointOptions"
              placeholder="筛选接口"
              show-search
              @change="refreshAll"
            />
          </div>
          <Table
            :columns="usageColumns"
            :data-source="usage"
            :loading="loading"
            :pagination="{ pageSize: 15, showSizeChanger: true }"
            :scroll="{ x: 1250 }"
            :row-key="usageRowKey"
          >
            <template #bodyCell="{ column, record }">
              <template v-if="column.key === 'bucket'">
                <span class="muted-text">{{ formatTime(record.bucket) }}</span>
              </template>
              <template v-else-if="column.key === 'endpoint'">
                <div class="usage-endpoint">
                  <strong>{{ record.endpointName }}</strong>
                  <span
                    >{{ serviceName(record.serviceCode) }} ·
                    {{ record.category }}</span
                  >
                </div>
              </template>
              <template v-else-if="column.key === 'caller'">
                <div class="caller-cell">
                  <Tag
                    :color="
                      record.callerType === 'consumer' ? 'blue' : 'orange'
                    "
                  >
                    {{ record.callerType === 'consumer' ? 'Consumer' : '匿名' }}
                  </Tag>
                  <span>{{ record.callerName }}</span>
                </div>
              </template>
            </template>
          </Table>
        </Tabs.TabPane>
      </Tabs>
    </Card>

    <Modal
      v-model:open="endpointModalOpen"
      :confirm-loading="endpointSaving"
      :title="editingEndpointId ? '编辑接口资产' : '新增接口资产'"
      width="720px"
      @ok="saveEndpoint"
    >
      <Form class="modal-form" layout="vertical">
        <div class="form-grid">
          <FormItem label="接口名称" required>
            <Input
              v-model:value="endpointForm.name"
              placeholder="例如：蓝字发票详情"
            />
          </FormItem>
          <FormItem label="业务分类" required>
            <Input
              v-model:value="endpointForm.category"
              placeholder="例如：票通开票"
            />
          </FormItem>
          <FormItem label="所属服务" required>
            <Select
              v-model:value="endpointForm.serviceCode"
              :options="serviceOptions"
            />
          </FormItem>
          <FormItem label="HTTP Method">
            <Select
              v-model:value="endpointForm.httpMethod"
              :options="
                ['*', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'].map((value) => ({
                  label: value,
                  value,
                }))
              "
            />
          </FormItem>
        </div>
        <FormItem label="路径模板" required>
          <Input
            v-model:value="endpointForm.pathTemplate"
            placeholder="/api/open/v1/blue-invoices/{invoiceCode}"
          />
        </FormItem>
        <div class="form-grid">
          <FormItem label="Kong Service ID">
            <Input
              v-model:value="endpointForm.kongServiceId"
              placeholder="可选"
            />
          </FormItem>
          <FormItem label="Kong Route ID">
            <Input
              v-model:value="endpointForm.kongRouteId"
              placeholder="可选"
            />
          </FormItem>
          <FormItem label="负责人">
            <Input
              v-model:value="endpointForm.owner"
              placeholder="部门或负责人"
            />
          </FormItem>
          <FormItem label="接口状态">
            <Select
              v-model:value="endpointForm.status"
              :options="[
                { label: '启用', value: 'enabled' },
                { label: '停用', value: 'disabled' },
                { label: '维护中', value: 'maintenance' },
              ]"
            />
          </FormItem>
        </div>
        <FormItem label="接口说明">
          <Input.TextArea v-model:value="endpointForm.description" :rows="3" />
        </FormItem>
        <div class="monitor-switch">
          <div>
            <strong>纳入调用统计</strong>
            <span>关闭后，Kong 推送的该接口事件将不再累计。</span>
          </div>
          <Switch v-model:checked="endpointForm.monitorEnabled" />
        </div>
      </Form>
    </Modal>

    <Modal
      v-model:open="consumerModalOpen"
      :confirm-loading="consumerSaving"
      title="编辑调用方资料"
      width="560px"
      @ok="saveConsumer"
    >
      <Form class="modal-form" layout="vertical">
        <FormItem label="展示名称">
          <Input
            v-model:value="consumerForm.displayName"
            placeholder="客户、系统或项目名称"
          />
        </FormItem>
        <FormItem label="状态">
          <Select
            v-model:value="consumerForm.status"
            :options="[
              { label: '活跃', value: 'active' },
              { label: '停用', value: 'inactive' },
            ]"
          />
        </FormItem>
        <FormItem label="备注">
          <Input.TextArea v-model:value="consumerForm.description" :rows="4" />
        </FormItem>
        <div v-if="editingConsumer" class="consumer-reference">
          <span>Kong Consumer</span>
          <code
            >{{ editingConsumer.username }} ·
            {{ editingConsumer.kongConsumerId }}</code
          >
        </div>
      </Form>
    </Modal>
  </Page>
</template>

<style scoped>
.open-api-page {
  --api-blue: #2563eb;
  --api-ink: #0f172a;
  --api-muted: #64748b;
  --api-line: rgba(148, 163, 184, 0.2);
}

.hero-panel {
  position: relative;
  min-height: 190px;
  overflow: hidden;
  padding: 34px 38px;
  color: #fff;
  background:
    linear-gradient(120deg, rgba(15, 23, 42, 0.98), rgba(18, 52, 102, 0.96)),
    #0f172a;
  border: 1px solid rgba(96, 165, 250, 0.2);
  border-radius: 18px;
  box-shadow: 0 24px 70px rgba(15, 23, 42, 0.18);
}

.hero-content {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 120px;
  gap: 24px;
}

.hero-content h1 {
  margin: 10px 0 8px;
  font-size: clamp(28px, 3vw, 40px);
  font-weight: 680;
  line-height: 1.2;
  letter-spacing: -1px;
}

.hero-content p {
  max-width: 640px;
  margin: 0;
  color: rgba(226, 232, 240, 0.78);
  font-size: 15px;
}

.hero-kicker,
.panel-kicker {
  color: #7dd3fc;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.18em;
}

.live-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 8px;
  background: #34d399;
  border-radius: 50%;
  box-shadow: 0 0 0 6px rgba(52, 211, 153, 0.12);
}

.hero-actions,
.health-pill {
  display: flex;
  align-items: center;
}

.hero-actions {
  gap: 14px;
}

.health-pill {
  gap: 8px;
  padding: 10px 14px;
  color: #bfdbfe;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(191, 219, 254, 0.15);
  border-radius: 999px;
}

.hero-orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(3px);
}

.hero-orb-one {
  top: -110px;
  right: 16%;
  width: 260px;
  height: 260px;
  background: rgba(37, 99, 235, 0.18);
}

.hero-orb-two {
  right: -60px;
  bottom: -150px;
  width: 300px;
  height: 300px;
  background: rgba(14, 165, 233, 0.12);
}

.filter-card {
  margin-top: 16px;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.05);
}

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.filter-title {
  display: flex;
  align-items: center;
  margin-right: 6px;
  gap: 8px;
  font-weight: 600;
}

.range-picker {
  min-width: 330px;
}

.filter-select {
  width: 150px;
}

.metric-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
  margin-top: 16px;
}

.metric-card {
  position: relative;
  overflow: hidden;
  border-radius: 14px;
  box-shadow: 0 8px 30px rgba(15, 23, 42, 0.045);
}

.metric-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.metric-icon {
  display: grid;
  width: 36px;
  height: 36px;
  font-size: 18px;
  border-radius: 10px;
  place-items: center;
}

.metric-label {
  color: var(--api-muted);
  font-size: 13px;
  font-weight: 600;
}

.metric-value {
  margin-top: 18px;
  color: var(--api-ink);
  font-size: 28px;
  font-weight: 720;
  line-height: 1;
  letter-spacing: -0.7px;
}

.metric-value small {
  margin-left: 2px;
  font-size: 14px;
}

.metric-note {
  margin-top: 9px;
  color: #94a3b8;
  font-size: 12px;
}

.metric-line {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 2px;
  opacity: 0.75;
}

.content-card {
  margin-top: 16px;
  border-radius: 16px;
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.055);
}

.tab-label {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}

.overview-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.65fr) minmax(300px, 0.75fr);
  gap: 16px;
}

.chart-panel {
  min-width: 0;
  padding: 20px;
  background: rgba(248, 250, 252, 0.48);
  border: 1px solid var(--api-line);
  border-radius: 14px;
}

.panel-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.panel-heading h3,
.table-toolbar h3 {
  margin: 4px 0 0;
  color: var(--api-ink);
  font-size: 17px;
  font-weight: 660;
}

.panel-kicker {
  color: #3b82f6;
  font-size: 9px;
}

.trend-chart {
  height: 330px;
}

.side-chart {
  height: 285px;
}

.caller-ranking {
  min-height: 285px;
  padding-top: 12px;
}

.caller-row {
  display: flex;
  align-items: center;
  padding: 9px 3px;
  gap: 10px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.12);
}

.rank-index {
  width: 22px;
  color: #94a3b8;
  font-size: 12px;
  font-weight: 700;
  text-align: center;
}

.rank-index.podium {
  color: #2563eb;
}

.caller-avatar {
  display: grid;
  width: 34px;
  height: 34px;
  color: #2563eb;
  background: rgba(37, 99, 235, 0.08);
  border-radius: 10px;
  place-items: center;
}

.caller-copy {
  display: flex;
  flex: 1;
  min-width: 0;
  flex-direction: column;
}

.caller-copy strong,
.asset-name strong,
.usage-endpoint strong {
  overflow: hidden;
  color: var(--api-ink);
  font-size: 13px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.caller-copy span,
.asset-name span,
.usage-endpoint span {
  color: #94a3b8;
  font-size: 11px;
}

.caller-row b {
  color: #334155;
  font-size: 13px;
}

.empty-state {
  display: flex;
  height: 250px;
  align-items: center;
  justify-content: center;
  color: #94a3b8;
  flex-direction: column;
  gap: 12px;
}

.empty-state svg {
  font-size: 28px;
}

.table-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 2px 20px;
  gap: 18px;
}

.table-toolbar p {
  margin: 5px 0 0;
  color: #94a3b8;
  font-size: 12px;
}

.asset-name,
.path-cell,
.caller-cell {
  display: flex;
  align-items: center;
}

.asset-name {
  gap: 11px;
}

.asset-name > div:last-child,
.usage-endpoint {
  display: flex;
  min-width: 0;
  flex-direction: column;
}

.asset-icon {
  display: grid;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  color: #2563eb;
  background: rgba(37, 99, 235, 0.08);
  border-radius: 10px;
  place-items: center;
}

.consumer-icon {
  color: #7c3aed;
  background: rgba(124, 58, 237, 0.08);
}

.path-cell {
  min-width: 0;
  gap: 8px;
}

.path-cell code,
.consumer-reference code {
  overflow: hidden;
  color: #475569;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.muted-text {
  color: #64748b;
  font-size: 12px;
}

.caller-cell {
  gap: 8px;
}

.endpoint-filter {
  width: 320px;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 16px;
}

.modal-form {
  margin-top: 20px;
}

.monitor-switch,
.consumer-reference {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px 16px;
  background: rgba(241, 245, 249, 0.7);
  border: 1px solid rgba(148, 163, 184, 0.18);
  border-radius: 12px;
}

.monitor-switch > div {
  display: flex;
  flex-direction: column;
}

.monitor-switch strong {
  color: #1e293b;
  font-size: 13px;
}

.monitor-switch span,
.consumer-reference span {
  color: #94a3b8;
  font-size: 11px;
}

.consumer-reference {
  align-items: flex-start;
  flex-direction: column;
  gap: 6px;
}

.spinning {
  animation: rotate 0.9s linear infinite;
}

@keyframes rotate {
  to {
    transform: rotate(360deg);
  }
}

@media (max-width: 1280px) {
  .metric-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 980px) {
  .hero-content {
    align-items: flex-start;
    flex-direction: column;
  }

  .overview-grid {
    grid-template-columns: 1fr;
  }

  .metric-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .range-picker {
    min-width: 100%;
  }
}

@media (max-width: 640px) {
  .hero-panel {
    padding: 26px 22px;
  }

  .hero-actions,
  .table-toolbar {
    width: 100%;
    align-items: stretch;
    flex-direction: column;
  }

  .metric-grid,
  .form-grid {
    grid-template-columns: 1fr;
  }

  .filter-select,
  .endpoint-filter {
    width: 100%;
  }
}

:deep(.ant-card-body) {
  padding: 20px;
}

:deep(.center-tabs > .ant-tabs-nav) {
  margin-bottom: 20px;
}

:deep(.center-tabs > .ant-tabs-nav::before) {
  border-color: rgba(148, 163, 184, 0.18);
}

:deep(.ant-table-wrapper .ant-table) {
  border-radius: 12px;
}

:global(.dark) .open-api-page {
  --api-ink: #e2e8f0;
  --api-muted: #94a3b8;
  --api-line: rgba(148, 163, 184, 0.14);
}

:global(.dark) .open-api-page .chart-panel {
  background: rgba(15, 23, 42, 0.38);
}

:global(.dark) .open-api-page .caller-row b,
:global(.dark) .open-api-page .path-cell code {
  color: #cbd5e1;
}

:global(.dark) .open-api-page .monitor-switch,
:global(.dark) .open-api-page .consumer-reference {
  background: rgba(15, 23, 42, 0.45);
}

:global(.dark) .open-api-page .monitor-switch strong {
  color: #e2e8f0;
}
</style>
