<!-- 模型管理页，负责模型能力、渠道归属和计费配置。 -->
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessageBox } from "element-plus";
import http, { apiError } from "@/api/http";
import PageHeader from "@/components/PageHeader.vue";
import AppIcon from "@/components/AppIcon.vue";
import AdminNumberInput from "@/components/AdminNumberInput.vue";
import { useToastStore } from "@/stores/toast";

type PriceField = [key: string, unit: string, label: string];

const toast = useToastStore();
const loading = ref(false);
const dialog = ref(false);
const items = ref<Record<string, any>[]>([]);
const channels = ref<Record<string, any>[]>([]);
const editing = ref("");
const query = reactive({ keyword: "", model_type: "", channel_id: "" });
const form = reactive<Record<string, any>>({});

const selectedChannel = computed(() => channels.value.find((channel) => channel.id === form.channel_id));

const priceFields = computed<PriceField[]>(() => {
  if (form.model_type === "text") {
    if (form.text_billing_mode === "per_token") {
      return [
        ["input", "M Token", "输入 Token"],
        ["output", "M Token", "输出 Token"],
      ];
    }
    return [["default", "次", "每次价格"]];
  }
  if (form.model_type === "image")
    return [
      ["1K", "次", "1K"],
      ["2K", "次", "2K"],
      ["4K", "次", "4K"],
    ];
  return [
    ["480p", "秒", "480p"],
    ["720p", "秒", "720p"],
    ["1080p", "秒", "1080p"],
  ];
});

function parsePrices(value: unknown): Record<string, any>[] {
  if (Array.isArray(value)) return value;
  if (typeof value !== "string") return [];
  try {
    const parsed = JSON.parse(value);
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

function hasTokenPrices(value: unknown) {
  return parsePrices(value).some((price) => ["input", "output"].includes(String(price.price_key).toLowerCase()));
}

function isTokenBillingRow(row: Record<string, any>) {
  return row.model_type === "text" && (row.text_billing_mode === "per_token" || hasTokenPrices(row.prices));
}

function displayPrices(row: Record<string, any>) {
  const prices = parsePrices(row.prices);
  if (row.model_type !== "text") return prices;
  const keys = isTokenBillingRow(row) ? ["input", "output"] : ["default"];
  return prices.filter((price) => keys.includes(String(price.price_key).toLowerCase()));
}

function reset(row?: Record<string, any>) {
  const rowPrices = parsePrices(row?.prices);
  const prices: Record<string, number> = {};
  for (const price of rowPrices) prices[price.price_key] = Number(price.price);
  const billingMode = hasTokenPrices(rowPrices) ? "per_token" : row?.text_billing_mode || "per_request";
  Object.assign(form, {
    channel_id: row?.channel_id || "",
    name: row?.name || "",
    model_type: row?.model_type || "text",
    multimodal: row?.multimodal || false,
    text_billing_mode: billingMode,
    enabled: row?.enabled ?? true,
    price_values: prices,
  });
  editing.value = row?.id || "";
  dialog.value = true;
}

async function fetchAllModels() {
  const result: Record<string, any>[] = [];
  let page = 1;
  while (true) {
    const { data } = await http.get("/models", { params: { ...query, page, page_size: 100 } });
    const current = data.data.items || [];
    result.push(...current);
    if (!current.length || result.length >= Number(data.data.total || 0)) return result;
    page += 1;
  }
}

async function load() {
  loading.value = true;
  try {
    const [models, { data: channelData }] = await Promise.all([
      fetchAllModels(),
      http.get("/resources/channels", { params: { page_size: 100 } }),
    ]);
    items.value = models.map((row: Record<string, any>) => ({ ...row, prices: parsePrices(row.prices) }));
    channels.value = channelData.data.items;
  } catch (error) {
    show(error);
  } finally {
    loading.value = false;
  }
}

async function save() {
  if (!form.channel_id || !form.name) {
    toast.warning("请填写模型名并选择渠道");
    return;
  }
  const prices = priceFields.value.map(([priceKey, unit]) => ({
    price_key: priceKey,
    unit,
    price: Number(Number(form.price_values[priceKey] || 0).toFixed(2)),
  }));
  try {
    const payload = { ...form, prices };
    delete payload.price_values;
    if (editing.value) await http.put(`/models/${editing.value}`, payload);
    else await http.post("/models", payload);
    toast.success(editing.value ? "模型已更新" : "模型已创建");
    dialog.value = false;
    load();
  } catch (error) {
    show(error);
  }
}

async function toggle(row: Record<string, any>, enabled: boolean) {
  try {
    await ElMessageBox.confirm(`确认${enabled ? "启用" : "停用"}模型 ${row.name}？`, "状态变更", { type: "warning" });
    await http.patch(`/resources/models/${row.id}/enabled`, { enabled });
    row.enabled = enabled;
    toast.success("模型状态已更新");
  } catch (error) {
    row.enabled = !enabled;
    if (error !== "cancel" && error !== "close") show(error);
  }
}

async function remove(row: Record<string, any>) {
  try {
    await ElMessageBox.confirm("模型将被软删除；已有使用记录不会删除。", "确认删除", { type: "warning" });
    await http.delete(`/resources/models/${row.id}`);
    toast.success("模型已删除");
    load();
  } catch (error) {
    if (error !== "cancel" && error !== "close") show(error);
  }
}

function show(error: unknown) {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText);
}

const typeText = (value: string) => ({ text: "文本", image: "图片", video: "视频" })[value] || value;
const priceLabel = (value: string) => ({ default: "基础", input: "输入", output: "输出" })[value] || value;
onMounted(load);
</script>

<template>
  <div class="page-stack management-page">
    <PageHeader
      title="模型管理"
      description="模型实际可用状态由模型和所属渠道的启用状态共同决定"
    >
      <el-button
        type="primary"
        @click="reset()"
        ><AppIcon name="Plus" />新建模型</el-button
      >
    </PageHeader>
    <section class="panel filter-bar">
      <el-input
        v-model="query.keyword"
        clearable
        placeholder="模型名"
      />
      <el-select
        v-model="query.model_type"
        clearable
        placeholder="模型类型"
      >
        <el-option
          label="文本"
          value="text"
        /><el-option
          label="图片"
          value="image"
        /><el-option
          label="视频"
          value="video"
        />
      </el-select>
      <el-select
        v-model="query.channel_id"
        clearable
        placeholder="所属渠道"
      >
        <el-option
          v-for="channel in channels"
          :key="channel.id"
          :label="channel.name"
          :value="channel.id"
        />
      </el-select>
      <el-button
        type="primary"
        @click="load"
        >查询</el-button
      >
    </section>
    <section
      class="panel table-panel"
      v-loading="loading"
    >
      <el-table :data="items">
        <el-table-column
          prop="name"
          label="模型名"
          min-width="150"
        />
        <el-table-column
          label="类型"
          width="90"
          ><template #default="{ row }"
            ><el-tag
              size="small"
              effect="plain"
              >{{ typeText(row.model_type) }}</el-tag
            ></template
          ></el-table-column
        >
        <el-table-column
          label="所属渠道"
          min-width="130"
          ><template #default="{ row }"
            ><el-tag
              size="small"
              type="info"
              effect="plain"
              >{{ row.channel_name }}</el-tag
            ></template
          ></el-table-column
        >
        <el-table-column
          label="能力"
          min-width="180"
        >
          <template #default="{ row }">
            <span v-if="row.model_type === 'text'">{{ row.multimodal ? "支持多模态" : "仅文本" }}</span>
            <span v-else-if="row.model_type === 'video'">视频生成</span>
            <span v-else>图片生成</span>
          </template>
        </el-table-column>
        <el-table-column
          label="模型价格"
          min-width="240"
        >
          <template #default="{ row }">
            <div
              v-if="displayPrices(row).length"
              class="price-list"
            >
              <span
                v-for="price in displayPrices(row)"
                :key="price.price_key"
                >{{ priceLabel(price.price_key) }}：{{ Number(price.price).toFixed(2) }} 积分/{{ price.unit }}</span
              >
            </div>
            <span v-else>--</span>
          </template>
        </el-table-column>
        <el-table-column
          label="启用"
          width="130"
          ><template #default="{ row }"
            ><el-tag
              v-if="!row.channel_enabled"
              size="small"
              type="warning"
              effect="plain"
              >渠道已禁用</el-tag
            ><el-switch
              v-else
              :model-value="row.enabled"
              @change="toggle(row, Boolean($event))" /></template
        ></el-table-column>
        <el-table-column
          label="操作"
          width="140"
          fixed="right"
          ><template #default="{ row }"
            ><el-button
              link
              type="primary"
              @click="reset(row)"
              >编辑</el-button
            ><el-button
              link
              type="danger"
              @click="remove(row)"
              >删除</el-button
            ></template
          ></el-table-column
        >
      </el-table>
      <el-empty
        v-if="!loading && !items.length"
        description="暂无模型"
      />
    </section>

    <el-dialog
      v-model="dialog"
      :title="editing ? '编辑模型' : '新建模型'"
      width="min(820px, calc(100vw - 32px))"
      class="managed-scroll-dialog model-dialog"
      modal-class="managed-scroll-overlay"
      :lock-scroll="true"
    >
      <el-form
        :model="form"
        label-position="top"
      >
        <div class="form-grid model-identity-grid">
          <el-form-item
            label="模型名"
            required
            ><el-input v-model.trim="form.name"
          /></el-form-item>
          <el-form-item
            label="所属渠道"
            required
            ><el-select
              v-model="form.channel_id"
              class="field-full"
              ><el-option
                v-for="channel in channels"
                :key="channel.id"
                :label="channel.name"
                :value="channel.id" /></el-select
          ></el-form-item>
          <el-form-item
            label="模型类型"
            required
            ><el-select
              v-model="form.model_type"
              class="field-full"
              ><el-option
                label="文本"
                value="text" /><el-option
                label="图片"
                value="image" /><el-option
                label="视频"
                value="video" /></el-select
          ></el-form-item>
        </div>
        <el-form-item
          v-if="form.model_type === 'text'"
          label="支持多模态"
          ><el-switch v-model="form.multimodal"
        /></el-form-item>
        <div
          v-if="form.model_type === 'text'"
          class="text-billing-settings"
        >
          <el-form-item
            label="计费方式"
            required
          >
            <el-radio-group v-model="form.text_billing_mode">
              <el-radio-button label="per_request">按次计费</el-radio-button>
              <el-radio-button label="per_token">按 Token 计费</el-radio-button>
            </el-radio-group>
          </el-form-item>
        </div>
        <section class="model-price-section">
          <header class="model-price-section__header">
            <div>
              <h3>模型价格</h3>
              <p>输入用户执行对应任务时实际扣除的积分。</p>
            </div>
          </header>
          <div class="model-price-grid">
            <div
              v-for="[key, unit, label] in priceFields"
              :key="key"
              class="model-price-card"
            >
              <el-form-item :label="`${label}计费（积分/${unit}）`">
                <AdminNumberInput
                  v-model="form.price_values[key]"
                  class="price-input"
                  :min="0"
                  :precision="2"
                />
              </el-form-item>
            </div>
          </div>
        </section>
      </el-form>
      <template #footer
        ><el-button @click="dialog = false">取消</el-button
        ><el-button
          type="primary"
          @click="save"
          >保存</el-button
        ></template
      >
    </el-dialog>
  </div>
</template>

<style scoped>
:global(.el-dialog.managed-scroll-dialog.model-dialog) {
  height: auto;
}
:global(.el-dialog.managed-scroll-dialog.model-dialog .el-dialog__body) {
  max-height: calc(100dvh - 180px);
  flex: 0 1 auto;
}
.model-identity-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.price-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  line-height: 1.45;
}
.model-price-section {
  margin-top: 2px;
  padding: 18px;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.model-price-section__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 16px;
}
.model-price-section h3 {
  font-size: 14px;
}
.model-price-section__header p {
  margin-top: 4px;
  color: var(--color-text-secondary);
  font-size: 12px;
  line-height: 1.5;
}
.model-price-rates {
  flex: 0 0 auto;
  display: grid;
  gap: 4px;
  color: var(--color-text-secondary);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.model-price-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}
.model-price-card {
  min-width: 0;
  padding: 14px;
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
}
.model-price-card :deep(.el-form-item) {
  margin-bottom: 12px;
}
.price-conversion {
  display: grid;
  gap: 8px;
  margin: 0;
}
.price-conversion > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.price-conversion dt {
  color: var(--color-text-secondary);
  font-size: 12px;
}
.price-conversion dd {
  margin: 0;
  color: var(--color-text);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.text-billing-settings {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(180px, 0.7fr);
  gap: 16px;
  align-items: start;
}
.field-hint {
  margin-top: 6px;
  color: var(--color-text-secondary);
  font-size: 12px;
  line-height: 1.5;
}
@media (max-width: 767px) {
  .model-identity-grid {
    grid-template-columns: 1fr;
  }
  .model-price-section {
    padding: 14px;
  }
  .model-price-section__header {
    flex-direction: column;
    gap: 10px;
  }
  .model-price-rates {
    text-align: left;
  }
  .model-price-grid {
    grid-template-columns: 1fr;
  }
  .text-billing-settings {
    grid-template-columns: 1fr;
  }
}
</style>
