<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";

// ---------- 后端接口返回的数据结构 ----------
// TypeScript 类型只在开发和构建时检查，不会出现在浏览器运行结果中。
type Product = {
  product_id: string;
  product_version_id: string;
  product_code: string;
  name: string;
  summary: string;
  version_no?: string;
  plans: Array<{ id: string; code: string; name: string }>;
};

type AgentAction = {
  type:
    | "open_enrollment"
    | "open_orders"
    | "open_policies"
    | "none";
  label: string;
  payload: Record<string, unknown>;
  confirmation_required: boolean;
};
type RuntimeEvent = {
  event:
    | "run.started"
    | "tool.completed"
    | "ui.ready"
    | "run.completed"
    | "run.failed";
  run_id: string;
  sequence: number;
  data: Record<string, unknown>;
};

type ChatMessage = {
  role: "user" | "assistant";
  text: string;
  html?: string;
  products?: Product[];
  actions?: AgentAction[];
  enrollmentContext?: {
    age?: number;
    relationship?: Relationship;
  };
};
type MainTab = "advisor" | "plans" | "coverage";
type Relationship = "SELF" | "PARENT" | "SPOUSE" | "CHILD";
type Quote = {
  quote_id: string;
  product_id: string;
  plan_id: string;
  premium_cents: number;
  currency: string;
  expires_at: string;
};
type Order = {
  order_id: string;
  order_no: string;
  status: string;
  amount_cents: number;
};
type Payment = {
  payment_id: string;
  payment_no: string;
  status: string;
};
type Policy = {
  policy_id: string;
  policy_no: string;
  order_id: string;
  order_no: string;
  product_name: string;
  plan_name: string;
  applicant: { name?: string; id_no?: string };
  insured: { name?: string; id_no?: string };
  status: string;
  premium_cents: number;
  coverage_start: string;
  coverage_end: string;
  issued_at: string;
};

// ---------- 页面响应式状态 ----------
// ref() 包装的值变化后，Vue 会自动更新模板中使用它的区域。
const apiBase =
  import.meta.env.VITE_API_BASE_URL ?? "http://127.0.0.1:8000/api/v1";
const mobile = ref("");
const code = ref("");
const imageCode = ref("");
const imageCodeInput = ref("");
const agreementAccepted = ref(false);
const codeCountdown = ref(0);
const codeRequested = ref(false);
let countdownTimer: ReturnType<typeof setInterval> | undefined;
const token = ref("");
const referralCode = new URLSearchParams(window.location.search)
  .get("ref")
  ?.trim()
  .toUpperCase() ?? "";
const contactMobile = ref("");
const products = ref<Product[]>([]);
const error = ref("");
const codeMessage = ref("");
const loading = ref(false);
const refreshing = ref(false);
const restoring = ref(true);
const agentInput = ref("我想给65岁的父亲买医疗险，常住成都，普通职业。");
const agentThreadId = ref("");
const agentMessages = ref<ChatMessage[]>([]);
const agentLoading = ref(false);
const agentProgress = ref("");
const serviceNo = ref("");
const activeTab = ref<MainTab>("advisor");
const selectedProduct = ref<Product | null>(null);
const quote = ref<Quote | null>(null);
const order = ref<Order | null>(null);
const payment = ref<Payment | null>(null);
const policy = ref<Policy | null>(null);
const policies = ref<Policy[]>([]);
const openedPolicy = ref<Policy | null>(null);
const businessLoading = ref(false);
const quotingProductId = ref("");
const quoteFeedback = ref("");
const enrollmentOpen = ref(false);
const preQuoteConfirming = ref(false);
const enrollmentStep = ref(1);
const applicantName = ref("");
const applicantIdNo = ref("");
const insuredName = ref("");
const insuredIdNo = ref("");
const insuredAge = ref(65);
const relationship = ref<Relationship>("PARENT");
const noticeAccepted = ref(false);
const disclosureConfirmed = ref(false);
const canRequestCode = computed(
  () => !loading.value && codeCountdown.value === 0,
);
const displayedProducts = computed(() => {
  if (!selectedProduct.value) return products.value;
  return [
    selectedProduct.value,
    ...products.value.filter(
      (product) => product.product_id !== selectedProduct.value?.product_id,
    ),
  ];
});

// ---------- 纯展示辅助函数 ----------
// 这些函数只做格式化和安全展示，不发起请求，也不修改后端业务状态。
function money(cents: number) {
  return `¥${(cents / 100).toFixed(2)}`;
}

function escapeHtml(value: string) {
  return value
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

// Agent 返回 Markdown 文本；渲染前先转义 HTML，再转换允许的少量 Markdown 语法。
// 这样既保留排版，也避免模型回复直接注入任意 HTML。
function renderInlineMarkdown(value: string) {
  return escapeHtml(value)
    .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
    .replace(/`([^`]+)`/g, "<code>$1</code>");
}

function markdownCells(line: string) {
  return line
    .replace(/^\s*\|/, "")
    .replace(/\|\s*$/, "")
    .split("|")
    .map((cell) => cell.trim());
}

function renderAgentMarkdown(source: string) {
  const lines = source.replace(/\r\n?/g, "\n").split("\n");
  const output: string[] = [];
  let listItems: string[] = [];

  const flushList = () => {
    if (listItems.length === 0) return;
    output.push(`<ul>${listItems.map((item) => `<li>${item}</li>`).join("")}</ul>`);
    listItems = [];
  };

  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index].trim();
    if (!line || /^-{3,}$/.test(line)) {
      flushList();
      continue;
    }

    const nextLine = lines[index + 1]?.trim() ?? "";
    if (
      line.includes("|") &&
      /^\|?\s*:?-{3,}/.test(nextLine)
    ) {
      flushList();
      const headers = markdownCells(line);
      const rows: string[][] = [];
      index += 2;
      while (index < lines.length && lines[index].includes("|")) {
        rows.push(markdownCells(lines[index]));
        index += 1;
      }
      index -= 1;
      output.push(
        `<div class="response-table-wrap"><table><thead><tr>${headers
          .map((cell) => `<th>${renderInlineMarkdown(cell)}</th>`)
          .join("")}</tr></thead><tbody>${rows
          .map(
            (row) =>
              `<tr>${row
                .map((cell) => `<td>${renderInlineMarkdown(cell)}</td>`)
                .join("")}</tr>`,
          )
          .join("")}</tbody></table></div>`,
      );
      continue;
    }

    const heading = line.match(/^#{1,4}\s+(.+)$/);
    if (heading) {
      flushList();
      output.push(`<h4>${renderInlineMarkdown(heading[1])}</h4>`);
      continue;
    }

    const listItem = line.match(/^(?:[-*•]|\d+[.)])\s+(.+)$/);
    if (listItem) {
      listItems.push(renderInlineMarkdown(listItem[1]));
      continue;
    }

    const quoteLine = line.match(/^>\s*(.+)$/);
    if (quoteLine) {
      flushList();
      output.push(`<blockquote>${renderInlineMarkdown(quoteLine[1])}</blockquote>`);
      continue;
    }

    flushList();
    output.push(`<p>${renderInlineMarkdown(line)}</p>`);
  }

  flushList();
  return output.join("");
}

function dateTime(value: string) {
  return new Intl.DateTimeFormat("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  }).format(new Date(value));
}

function policyStatus(status: string) {
  return {
    ACTIVE: "保障中",
    PENDING: "待生效",
    EXPIRED: "已到期",
    SURRENDERING: "退保处理中",
    SURRENDERED: "已退保",
  }[status] ?? status;
}

function relationshipName(value: Relationship) {
  return {
    SELF: "本人",
    PARENT: "父母",
    SPOUSE: "配偶",
    CHILD: "子女",
  }[value];
}

function inferEnrollmentContext(text: string) {
  const ageMatch = text.match(/(\d{1,3})\s*岁/);
  const age = ageMatch ? Number(ageMatch[1]) : undefined;
  let inferredRelationship: Relationship | undefined;
  if (/(父亲|母亲|父母|爸爸|妈妈)/.test(text)) inferredRelationship = "PARENT";
  else if (/(爱人|配偶|丈夫|妻子|老公|老婆)/.test(text)) inferredRelationship = "SPOUSE";
  else if (/(孩子|子女|儿子|女儿)/.test(text)) inferredRelationship = "CHILD";
  else if (/(本人|自己|我想给我|为自己)/.test(text)) inferredRelationship = "SELF";
  return {
    age: age !== undefined && age >= 0 && age <= 120 ? age : undefined,
    relationship: inferredRelationship,
  };
}

function maskIdNo(value: string) {
  if (value.length < 10) return value;
  return `${value.slice(0, 6)}********${value.slice(-4)}`;
}

function createImageCode() {
  const alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
  const bytes = new Uint8Array(4);
  crypto.getRandomValues(bytes);
  imageCode.value = Array.from(
    bytes,
    (value) => alphabet[value % alphabet.length],
  ).join("");
  imageCodeInput.value = "";
}

function startCodeCountdown() {
  if (countdownTimer) clearInterval(countdownTimer);
  codeCountdown.value = 60;
  countdownTimer = setInterval(() => {
    codeCountdown.value -= 1;
    if (codeCountdown.value <= 0 && countdownTimer) {
      clearInterval(countdownTimer);
      countdownTimer = undefined;
    }
  }, 1000);
}

function validateMobile() {
  if (!/^1\d{10}$/.test(mobile.value)) {
    error.value = "请输入正确的11位手机号";
    return false;
  }
  return true;
}

// ---------- HTTP 请求与登录态 ----------
// authorizedFetch 会自动附带 Token；遇到登录过期时只尝试刷新一次。
async function apiFetch(path: string, init: RequestInit = {}) {
  return fetch(`${apiBase}${path}`, {
    ...init,
    credentials: "include",
  });
}

async function responseError(response: Response, fallback: string) {
  try {
    const body = await response.json();
    return body.error?.message ?? fallback;
  } catch {
    return fallback;
  }
}

async function authorizedFetch(path: string, init: RequestInit = {}, allowRefresh = true) {
  const headers = new Headers(init.headers);
  headers.set("Authorization", `Bearer ${token.value}`);
  const response = await apiFetch(path, { ...init, headers });
  if (response.status === 401 && allowRefresh && (await refreshAccessToken())) {
    return authorizedFetch(path, init, false);
  }
  return response;
}

async function requestCode() {
  error.value = "";
  codeMessage.value = "";
  if (!validateMobile()) return;
  if (
    imageCodeInput.value.trim().toUpperCase() !== imageCode.value.toUpperCase()
  ) {
    error.value = "图形验证码不正确，请重新输入";
    createImageCode();
    return;
  }
  loading.value = true;
  try {
    const response = await apiFetch("/h5/auth/request-code", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ mobile: mobile.value }),
    });
    if (!response.ok) {
      throw new Error(await responseError(response, "验证码请求失败"));
    }
    const body = await response.json();
    const validMinutes = Math.max(1, Math.floor(body.data.expires_in / 60));
    codeRequested.value = true;
    codeMessage.value = `验证码已发送，${validMinutes}分钟内有效`;
    startCodeCountdown();
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "请求失败";
    createImageCode();
  } finally {
    loading.value = false;
  }
}

async function login() {
  error.value = "";
  if (!validateMobile()) return;
  if (!codeRequested.value) {
    error.value = "请先获取短信验证码";
    return;
  }
  if (!/^\d{6}$/.test(code.value)) {
    error.value = "请输入6位短信验证码";
    return;
  }
  if (!agreementAccepted.value) {
    error.value = "请阅读并同意服务协议与隐私政策";
    return;
  }
  loading.value = true;
  try {
    const response = await apiFetch("/h5/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        mobile: mobile.value,
        code: code.value,
        referral_code: referralCode || undefined,
      }),
    });
    if (!response.ok) {
      throw new Error(await responseError(response, "登录失败"));
    }
    const body = await response.json();
    token.value = body.data.tokens.access_token;
    mobile.value = body.data.user.mobile;
    contactMobile.value = body.data.user.mobile;
    code.value = "";
    codeMessage.value = "";
    await loadProducts();
    await loadBusiness();
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "请求失败";
  } finally {
    loading.value = false;
  }
}

async function refreshAccessToken() {
  const response = await apiFetch("/h5/auth/refresh", { method: "POST" });
  if (!response.ok) {
    token.value = "";
    products.value = [];
    return false;
  }
  const body = await response.json();
  token.value = body.data.tokens.access_token;
  mobile.value = body.data.user.mobile;
  contactMobile.value = body.data.user.mobile;
  return true;
}

async function loadProducts(allowRefresh = true) {
  const response = await apiFetch("/h5/products", {
    headers: { Authorization: `Bearer ${token.value}` },
  });
  if (response.status === 401 && allowRefresh && (await refreshAccessToken())) {
    return loadProducts(false);
  }
  if (!response.ok) {
    if (response.status === 401) token.value = "";
    throw new Error(await responseError(response, "产品加载失败"));
  }
  const body = await response.json();
  products.value = body.data.items;
}

// ---------- 客户保障顾问 Agent ----------
// 先创建/复用会话，再通过 SSE 接收运行开始、工具完成、UI 就绪和最终回答。
async function sendAgentMessage() {
  const text = agentInput.value.trim();
  if (!text || agentLoading.value) return;
  agentLoading.value = true;
  agentProgress.value = "正在理解保障需求";
  error.value = "";
  agentMessages.value.push({ role: "user", text });
  agentInput.value = "";
  try {
    if (!agentThreadId.value) {
      const threadResponse = await authorizedFetch("/agent/threads", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title: "智能投保咨询" }),
      });
      if (!threadResponse.ok) {
        throw new Error(await responseError(threadResponse, "创建咨询会话失败"));
      }
      agentThreadId.value = (await threadResponse.json()).data.id;
    }
    const response = await authorizedFetch(
      `/agent/threads/${agentThreadId.value}/messages/stream`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          content: { type: "text", text },
          client_message_id: crypto.randomUUID(),
        }),
      },
    );
    if (!response.ok) {
      throw new Error(await responseError(response, "智能体响应失败"));
    }
    const data = await consumeAgentStream(response);
    const assistantText = data.assistant_message.text as string;
    const cards = (data.assistant_message.cards ?? []) as Array<{
      type: string;
      items?: Product[];
    }>;
    const cardItems = cards
      .filter((card) => card.type === "product_recommendations")
      .flatMap((card) => card.items ?? []);
    const actions = (data.assistant_message.actions ?? []) as AgentAction[];
    agentMessages.value.push({
      role: "assistant",
      text: assistantText,
      html: renderAgentMarkdown(assistantText),
      enrollmentContext: inferEnrollmentContext(text),
      products: cardItems.slice(0, 2),
      actions,
    });
    serviceNo.value = data.service_no ?? "";
  } catch (reason) {
    if (!agentInput.value.trim()) agentInput.value = text;
    error.value = reason instanceof Error ? reason.message : "智能体响应失败";
  } finally {
    agentLoading.value = false;
    agentProgress.value = "";
  }
}

async function consumeAgentStream(response: Response) {
  if (!response.body) throw new Error("浏览器不支持流式响应");
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let completed: Record<string, any> | null = null;
  while (true) {
    const { done, value } = await reader.read();
    buffer += decoder.decode(value, { stream: !done });
    const packets = buffer.split("\n\n");
    buffer = packets.pop() ?? "";
    for (const packet of packets) {
      const dataLine = packet
        .split("\n")
        .find((line) => line.startsWith("data: "));
      if (!dataLine) continue;
      const event = JSON.parse(dataLine.slice(6)) as RuntimeEvent;
      if (event.event === "tool.completed") {
        agentProgress.value = "正在核验产品与业务规则";
      } else if (event.event === "ui.ready") {
        agentProgress.value = "正在生成可操作的保障方案";
      } else if (event.event === "run.failed") {
        throw new Error(String(event.data.message ?? "智能体响应失败"));
      } else if (event.event === "run.completed") {
        completed = event.data.result as Record<string, any>;
      }
    }
    if (done) break;
  }
  if (!completed) throw new Error("智能体运行未正常结束");
  return completed;
}

// Action 不是模型生成的任意链接，而是后端 Harness 返回的受控动作。
async function executeAgentAction(
  action: AgentAction,
  context?: ChatMessage["enrollmentContext"],
) {
  if (action.type === "open_orders" || action.type === "open_policies") {
    activeTab.value = "coverage";
    await loadBusiness();
    return;
  }
  if (action.type !== "open_enrollment") return;
  const productCode =
    typeof action.payload.product_code === "string"
      ? action.payload.product_code
      : "";
  const product = products.value.find(
    (item) => item.product_code === productCode,
  );
  if (!product) {
    error.value = "推荐产品已不在当前在售目录，请重新咨询";
    return;
  }
  const age =
    typeof action.payload.age === "number"
      ? action.payload.age
      : context?.age;
  const rawRelationship = action.payload.relationship;
  const actionRelationship = (
    ["SELF", "PARENT", "SPOUSE", "CHILD"] as const
  ).find((item) => item === rawRelationship);
  await startDirectEnrollment(product, {
    age,
    relationship: actionRelationship ?? context?.relationship,
  });
}

function viewProduct(product: Product) {
  selectedProduct.value = product;
  activeTab.value = "plans";
  window.scrollTo({ top: 0, behavior: "smooth" });
}

async function startDirectEnrollment(
  product: Product,
  context?: ChatMessage["enrollmentContext"],
) {
  selectedProduct.value = product;
  if (context?.age !== undefined) insuredAge.value = context.age;
  if (context?.relationship) relationship.value = context.relationship;
  quote.value = null;
  resetEnrollmentFlow();
  preQuoteConfirming.value = true;
  enrollmentOpen.value = true;
  error.value = "";
  await nextTick();
  document
    .querySelector<HTMLElement>(".enrollment-body")
    ?.scrollTo({ top: 0 });
}

async function confirmDirectEnrollment() {
  if (!selectedProduct.value) return;
  if (
    !Number.isInteger(insuredAge.value) ||
    insuredAge.value < 0 ||
    insuredAge.value > 120
  ) {
    error.value = "请输入0至120之间的有效年龄";
    return;
  }
  await createQuote(selectedProduct.value);
}

function openPolicyDocument(item: Policy) {
  openedPolicy.value = item;
}

function closePolicyDocument() {
  openedPolicy.value = null;
}

function printPolicyDocument() {
  window.print();
}

function resetEnrollmentFlow() {
  enrollmentStep.value = 1;
  noticeAccepted.value = false;
  disclosureConfirmed.value = false;
  order.value = null;
  payment.value = null;
  policy.value = null;
}

// ---------- 确定性投保流程 ----------
// 报价、草稿、确认、订单、支付和出单都调用普通业务接口，不由模型直接改状态。
async function setEnrollmentStep(step: number) {
  enrollmentStep.value = step;
  await nextTick();
  document
    .querySelector<HTMLElement>(".enrollment-body")
    ?.scrollTo({ top: 0, behavior: "smooth" });
}

async function createQuote(product: Product) {
  businessLoading.value = true;
  quotingProductId.value = product.product_id;
  quoteFeedback.value = "正在校验投保资格并试算保费…";
  error.value = "";
  try {
    const response = await authorizedFetch("/h5/quotes", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        product_id: product.product_id,
        plan_id: product.plans[0].id,
        insured: {
          age: insuredAge.value,
          region_code: "510100",
          occupation_code: "GENERAL",
        },
        relationship: relationship.value,
      }),
    });
    if (!response.ok) throw new Error(await responseError(response, "报价失败"));
    selectedProduct.value = product;
    quote.value = (await response.json()).data;
    resetEnrollmentFlow();
    preQuoteConfirming.value = false;
    enrollmentOpen.value = true;
    quoteFeedback.value = "";
    await setEnrollmentStep(1);
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "报价失败";
    quoteFeedback.value = error.value;
  } finally {
    businessLoading.value = false;
    quotingProductId.value = "";
  }
}

function validateEnrollmentPeople() {
  error.value = "";
  if (!applicantName.value.trim() || !insuredName.value.trim()) {
    error.value = "请填写投保人和被保人姓名";
    return false;
  }
  const idPattern = /^\d{17}[\dXx]$/;
  if (
    !idPattern.test(applicantIdNo.value) ||
    !idPattern.test(insuredIdNo.value)
  ) {
    error.value = "请输入正确的18位身份证号码";
    return false;
  }
  if (!/^1\d{10}$/.test(contactMobile.value)) {
    error.value = "请输入正确的11位联系手机号";
    return false;
  }
  return true;
}

async function goToNotice() {
  if (!validateEnrollmentPeople()) return;
  error.value = "";
  await setEnrollmentStep(2);
}

async function goToConfirmation() {
  error.value = "";
  if (!noticeAccepted.value || !disclosureConfirmed.value) {
    error.value = "请阅读投保须知并完成两项确认";
    return;
  }
  await setEnrollmentStep(3);
}

async function confirmAndCreateOrder() {
  if (!quote.value) return;
  businessLoading.value = true;
  error.value = "";
  try {
    const draftResponse = await authorizedFetch("/h5/enrollment-drafts", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        quote_id: quote.value.quote_id,
        applicant: {
          name: applicantName.value.trim(),
          id_no: applicantIdNo.value.toUpperCase(),
        },
        insured: {
          name: insuredName.value.trim(),
          id_no: insuredIdNo.value.toUpperCase(),
        },
        contact: { mobile: contactMobile.value },
      }),
    });
    if (!draftResponse.ok) {
      throw new Error(await responseError(draftResponse, "投保草稿创建失败"));
    }
    const draft = (await draftResponse.json()).data;
    const confirmationResponse = await authorizedFetch(
      `/h5/enrollment-drafts/${draft.draft_id}/confirmation`,
      { method: "POST" },
    );
    if (!confirmationResponse.ok) {
      throw new Error(await responseError(confirmationResponse, "确认失败"));
    }
    const confirmation = (await confirmationResponse.json()).data;
    const orderResponse = await authorizedFetch("/h5/orders", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Idempotency-Key": crypto.randomUUID(),
      },
      body: JSON.stringify({
        draft_id: draft.draft_id,
        confirmation_token: confirmation.confirmation_token,
      }),
    });
    if (!orderResponse.ok) {
      throw new Error(await responseError(orderResponse, "订单创建失败"));
    }
    order.value = (await orderResponse.json()).data;
    const paymentCreated = await startPayment();
    if (paymentCreated) await setEnrollmentStep(4);
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "订单创建失败";
  } finally {
    businessLoading.value = false;
  }
}

async function startPayment() {
  if (!order.value) return false;
  businessLoading.value = true;
  error.value = "";
  try {
    const response = await authorizedFetch(
      `/h5/orders/${order.value.order_id}/payments`,
      {
        method: "POST",
        headers: { "Idempotency-Key": crypto.randomUUID() },
      },
    );
    if (!response.ok) throw new Error(await responseError(response, "支付发起失败"));
    payment.value = (await response.json()).data;
    return true;
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "支付发起失败";
    return false;
  } finally {
    businessLoading.value = false;
  }
}

async function completePayment() {
  if (!payment.value) return;
  businessLoading.value = true;
  error.value = "";
  try {
    const response = await authorizedFetch(
      `/dev/mock-payments/${payment.value.payment_id}/complete`,
      { method: "POST" },
    );
    if (!response.ok) throw new Error(await responseError(response, "支付回调失败"));
    const body = await response.json();
    payment.value.status = body.data.payment_status;
    if (order.value) order.value.status = body.data.order_status;
    policy.value = body.data.policy;
    await loadBusiness();
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "支付回调失败";
  } finally {
    businessLoading.value = false;
  }
}

async function closeEnrollment() {
  enrollmentOpen.value = false;
  preQuoteConfirming.value = false;
  error.value = "";
  await loadBusiness();
}

async function viewCoverageAfterPayment() {
  enrollmentOpen.value = false;
  activeTab.value = "coverage";
  await loadBusiness();
  window.scrollTo({ top: 0, behavior: "smooth" });
}

async function loadBusiness() {
  if (!token.value) return;
  const [ordersResponse, policiesResponse] = await Promise.all([
    authorizedFetch("/h5/orders"),
    authorizedFetch("/h5/policies"),
  ]);
  if (ordersResponse.ok) {
    const items = (await ordersResponse.json()).data.items as Order[];
    order.value = items[0] ?? null;
  }
  if (policiesResponse.ok) {
    const items = (await policiesResponse.json()).data.items as Policy[];
    policies.value = items;
  }
}

async function refreshProducts() {
  refreshing.value = true;
  error.value = "";
  try {
    await loadProducts();
  } catch (reason) {
    error.value = reason instanceof Error ? reason.message : "产品加载失败";
  } finally {
    refreshing.value = false;
  }
}

async function logout() {
  try {
    if (token.value) {
      await apiFetch("/h5/auth/logout", {
        method: "POST",
        headers: { Authorization: `Bearer ${token.value}` },
      });
    }
  } finally {
    token.value = "";
    contactMobile.value = "";
    products.value = [];
    error.value = "";
    agentThreadId.value = "";
    agentMessages.value = [];
    serviceNo.value = "";
    activeTab.value = "advisor";
    selectedProduct.value = null;
    quote.value = null;
    order.value = null;
    payment.value = null;
    policy.value = null;
    policies.value = [];
    openedPolicy.value = null;
    quotingProductId.value = "";
    quoteFeedback.value = "";
    enrollmentOpen.value = false;
    preQuoteConfirming.value = false;
    enrollmentStep.value = 1;
    applicantName.value = "";
    applicantIdNo.value = "";
    insuredName.value = "";
    insuredIdNo.value = "";
    relationship.value = "PARENT";
    noticeAccepted.value = false;
    disclosureConfirmed.value = false;
    codeRequested.value = false;
    codeCountdown.value = 0;
    agreementAccepted.value = false;
    if (countdownTimer) {
      clearInterval(countdownTimer);
      countdownTimer = undefined;
    }
    createImageCode();
  }
}

// 页面初始化时恢复登录态并加载产品、订单和保单；组件销毁时清理验证码计时器。
onMounted(async () => {
  createImageCode();
  // 清理早期版本遗留的持久化Access Token，当前版本只在内存中保存。
  localStorage.removeItem("zbt_h5_token");
  try {
    if (await refreshAccessToken()) await loadProducts(false);
    if (token.value) await loadBusiness();
  } catch {
    token.value = "";
    products.value = [];
  } finally {
    restoring.value = false;
  }
});

onBeforeUnmount(() => {
  if (countdownTimer) clearInterval(countdownTimer);
});
</script>

<template>
  <main class="phone-shell">
    <section v-if="restoring || !token" class="hero">
      <div class="brand-line"><span class="seal">智</span> 智保通</div>
      <p class="eyebrow">AI 智能保险服务平台</p>
      <h1>把复杂条款，<br /><em>说成一句明白话。</em></h1>
      <div class="hero-orbit" aria-hidden="true"></div>
    </section>

    <section v-if="restoring" class="login-card session-loading" aria-live="polite">
      <p class="step">00 / 会话检查</p>
      <h2>正在恢复安全会话…</h2>
      <p class="hint">仅从安全 Cookie 轮换登录凭证。</p>
    </section>

    <section v-else-if="!token" class="login-card">
      <div class="login-heading">
        <div>
          <p class="step">01 / 安全登录</p>
          <h2>欢迎使用智保通</h2>
        </div>
        <span class="secure-badge"><i></i>安全连接</span>
      </div>
      <p class="login-intro">登录后继续咨询方案、查询订单与管理个人保单。</p>
      <p v-if="referralCode" class="referral-banner">
        <span>邀</span> 已识别专属服务邀请 · {{ referralCode }}
      </p>

      <form class="login-form" @submit.prevent="login">
        <label>
          手机号
          <span class="field-shell">
            <span class="field-prefix">+86</span>
            <input
              v-model.trim="mobile"
              inputmode="tel"
              autocomplete="tel"
              maxlength="11"
              placeholder="请输入手机号"
              aria-label="手机号"
            />
          </span>
        </label>

        <label>
          图形验证码
          <span class="field-row">
            <input
              v-model.trim="imageCodeInput"
              class="image-code-input"
              maxlength="4"
              autocomplete="off"
              placeholder="请输入图中字符"
              aria-label="图形验证码"
            />
            <button
              type="button"
              class="captcha"
              aria-label="刷新图形验证码"
              title="看不清？点击换一张"
              @click="createImageCode"
            >
              <span
                v-for="(letter, index) in imageCode"
                :key="`${letter}-${index}`"
                :style="{ transform: `rotate(${index % 2 === 0 ? -8 : 7}deg) translateY(${index % 3 - 1}px)` }"
              >{{ letter }}</span>
              <i></i><i></i>
            </button>
          </span>
          <small class="field-help">看不清？点击图片更换</small>
        </label>

        <label>
          短信验证码
          <span class="field-row">
            <input
              v-model.trim="code"
              inputmode="numeric"
              autocomplete="one-time-code"
              maxlength="6"
              placeholder="请输入6位验证码"
              aria-label="短信验证码"
            />
            <button
              type="button"
              class="send-code"
              :disabled="!canRequestCode"
              @click="requestCode"
            >
              {{ codeCountdown > 0 ? `${codeCountdown}s 后重发` : "获取验证码" }}
            </button>
          </span>
        </label>

        <label class="agreement">
          <input v-model="agreementAccepted" type="checkbox" />
          <span>我已阅读并同意<a href="#service-agreement">《用户服务协议》</a>和<a href="#privacy-policy">《隐私政策》</a></span>
        </label>

        <p v-if="codeMessage" class="success status-message" aria-live="polite">
          <span>✓</span>{{ codeMessage }}
        </p>
        <p v-if="error" class="error status-message" role="alert">
          <span>!</span>{{ error }}
        </p>

        <button class="login-submit" :disabled="loading" type="submit">
          {{ loading ? "正在安全登录…" : "登录" }}
          <span aria-hidden="true">→</span>
        </button>
      </form>
      <p class="privacy-note"><span>◇</span> 你的个人信息将被加密保护</p>
    </section>

    <div v-else class="app-shell">
      <header class="app-header">
        <div class="compact-brand">
          <span class="seal">智</span>
          <div><strong>智保通</strong><small>懂你的保险顾问</small></div>
        </div>
        <button class="header-action" aria-label="退出登录" @click="logout">
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M10 4H5v16h5M14 8l4 4-4 4M18 12H9" />
          </svg>
        </button>
      </header>

      <div class="tab-content">
        <section v-if="activeTab === 'advisor'" class="tab-page advisor-page">
          <div class="page-heading">
            <div><p class="step">AI INSURANCE ADVISOR</p><h2>智能顾问</h2></div>
            <span class="online-pill"><i></i>在线</span>
          </div>
          <p class="page-lead">说说你的保障需求，我会查询在售产品并给出适合的候选方案。</p>

          <div class="quick-prompts">
            <button @click="agentInput = '我想给65岁的父亲买医疗险，常住成都，普通职业。'">父母医疗</button>
            <button @click="agentInput = '我想给一家三口配置意外保障，请帮我推荐。'">家庭意外</button>
            <button @click="agentInput = '我想了解基础医疗保障，年龄32岁。'">成人医疗</button>
          </div>

          <div class="agent-panel">
            <div class="agent-head">
              <span class="agent-mark">AI</span>
              <div><strong>你好，我是智保通顾问</strong><small>在线服务 · 安全连接</small></div>
            </div>
            <div class="messages" aria-live="polite">
              <div v-if="agentMessages.length === 0" class="welcome-message">
                <strong>我可以帮你做什么？</strong>
                <p>告诉我为谁投保、年龄、常住地区和职业，我会先理解需求，再推荐方案。</p>
              </div>
              <div
                v-for="(message, index) in agentMessages"
                :key="index"
                :class="['message', message.role]"
              >
                <div class="message-meta">
                  <span v-if="message.role === 'assistant'">AI</span>
                  <small>{{ message.role === "user" ? "你" : "智保通顾问" }}</small>
                </div>
                <div
                  v-if="message.role === 'assistant'"
                  class="assistant-content"
                  v-html="message.html"
                ></div>
                <p v-else>{{ message.text }}</p>
                <div v-if="message.products?.length" class="recommendation-list">
                  <div
                    v-for="(product, productIndex) in message.products"
                    :key="product.product_id"
                    class="recommendation-card"
                  >
                    <div>
                      <span>{{ productIndex === 0 ? "优先推荐" : "备选方案" }}</span>
                      <strong>{{ product.name }}</strong>
                      <small>{{ product.summary }}</small>
                    </div>
                    <button @click="startDirectEnrollment(product, message.enrollmentContext)">
                      立即投保 →
                    </button>
                  </div>
                </div>
                <div
                  v-if="message.actions?.length && !message.products?.length"
                  class="agent-actions"
                >
                  <button
                    v-for="action in message.actions.filter((item) => item.type !== 'none')"
                    :key="`${action.type}-${action.label}`"
                    type="button"
                    @click="executeAgentAction(action, message.enrollmentContext)"
                  >
                    {{ action.label }} <span>→</span>
                  </button>
                </div>
              </div>
            </div>
            <div class="agent-composer">
              <textarea
                v-model="agentInput"
                rows="3"
                aria-label="投保需求"
                placeholder="请描述你的投保需求…"
              ></textarea>
              <button
                aria-label="发送消息"
                :disabled="agentLoading || !agentInput.trim()"
                @click="sendAgentMessage"
              >
                <svg viewBox="0 0 24 24" aria-hidden="true">
                  <path d="m4 5 16 7-16 7 3-7-3-7Zm3 7h13" />
                </svg>
              </button>
            </div>
            <p v-if="agentLoading" class="thinking"><i></i><i></i><i></i> {{ agentProgress }}</p>
            <p v-if="serviceNo" class="trace">服务流水号 · {{ serviceNo }}</p>
          </div>
          <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
        </section>

        <section v-else-if="activeTab === 'plans'" class="tab-page plans-page">
          <div class="page-heading">
            <div><p class="step">PROTECTION PLANS</p><h2>保障方案</h2></div>
            <button class="refresh-button" :disabled="refreshing" @click="refreshProducts">
              {{ refreshing ? "刷新中" : "刷新" }}
            </button>
          </div>
          <p class="page-lead">先填写被保人的基本情况，再查看确定性保费。</p>

          <div class="quote-conditions">
            <div class="condition-title"><span>01</span><strong>被保人信息</strong></div>
            <label>
              与投保人关系
              <span class="relation-options">
                <button
                  v-for="item in (['SELF', 'PARENT', 'SPOUSE', 'CHILD'] as Relationship[])"
                  :key="item"
                  :class="{ active: relationship === item }"
                  @click="relationship = item"
                >
                  {{ relationshipName(item) }}
                </button>
              </span>
            </label>
            <label>
              被保人年龄
              <input v-model.number="insuredAge" type="number" min="0" max="120" />
            </label>
            <div class="fixed-conditions">
              <span>常住地区：成都市</span><span>职业类别：普通职业</span>
            </div>
          </div>

          <div class="product-list">
            <div
              v-for="(product, index) in displayedProducts"
              :key="product.product_code"
              :class="['product-card', { recommended: selectedProduct?.product_id === product.product_id }]"
            >
              <div class="product-topline">
                <span>{{ String(index + 1).padStart(2, "0") }}</span>
                <em v-if="selectedProduct?.product_id === product.product_id">顾问推荐</em>
              </div>
              <h3>{{ product.name }}</h3>
              <p>{{ product.summary }}</p>
              <div class="plans">
                <span v-for="plan in product.plans" :key="plan.code">{{ plan.name }}</span>
              </div>
              <ul class="benefit-list">
                <li>电子保单即时查询</li>
                <li>支持家庭成员投保</li>
                <li>保费由规则引擎确定</li>
              </ul>
              <button
                class="product-action"
                :disabled="businessLoading"
                :aria-busy="quotingProductId === product.product_id"
                @click="createQuote(product)"
              >
                {{ quotingProductId === product.product_id ? "正在试算…" : "测算保费并投保" }} <span>→</span>
              </button>
            </div>
          </div>
          <div v-if="quoteFeedback" :class="['quote-feedback', { failed: !businessLoading }]">
            <span>{{ businessLoading ? "···" : "!" }}</span>{{ quoteFeedback }}
          </div>
          <p v-if="products.length === 0" class="empty-state">暂时没有可投保产品。</p>
          <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
        </section>

        <section v-else class="tab-page coverage-page">
          <div class="page-heading">
            <div><p class="step">MY PROTECTION</p><h2>我的保障</h2></div>
            <span class="record-count">{{ policies.length > 0 ? `${policies.length}张保单` : "暂无保单" }}</span>
          </div>
          <p class="page-lead">集中查看投保订单与已经生效的保障。</p>

          <div v-if="policies.length > 0" class="policy-list">
            <article v-for="(item, index) in policies" :key="item.policy_id" class="policy-summary">
              <div class="policy-glow"></div>
              <div class="policy-heading">
                <span>电子保单 · {{ String(index + 1).padStart(2, "0") }}</span>
                <em>{{ policyStatus(item.status) }}</em>
              </div>
              <h3>{{ item.product_name }}</h3>
              <p class="policy-plan">{{ item.plan_name }}</p>
              <p class="policy-number">{{ item.policy_no }}</p>
              <div class="policy-data">
                <div><small>年度保费</small><strong>{{ money(item.premium_cents) }}</strong></div>
                <div><small>保障期限</small><strong>{{ item.coverage_start }} 至 {{ item.coverage_end }}</strong></div>
              </div>
              <button type="button" @click="openPolicyDocument(item)">
                查看电子保单 <span>→</span>
              </button>
            </article>
          </div>

          <div v-if="!order && policies.length === 0" class="coverage-empty">
            <span class="empty-shield">◇</span>
            <h3>还没有保障记录</h3>
            <p>先和智能顾问聊聊，找到适合你的保障方案。</p>
            <button @click="activeTab = 'advisor'">咨询智能顾问</button>
          </div>
          <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
        </section>
      </div>

      <nav class="bottom-nav" aria-label="主导航">
        <button :class="{ active: activeTab === 'advisor' }" @click="activeTab = 'advisor'">
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M7 8h10M7 12h7M8 18l-4 2 1-4a8 8 0 1 1 3 2Z" />
          </svg>
          <span>智能顾问</span>
        </button>
        <button :class="{ active: activeTab === 'plans' }" @click="activeTab = 'plans'">
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M12 3 4 6v5c0 5 3 8 8 10 5-2 8-5 8-10V6l-8-3Zm-3 9 2 2 4-5" />
          </svg>
          <span>保障方案</span>
        </button>
        <button :class="{ active: activeTab === 'coverage' }" @click="activeTab = 'coverage'">
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M7 3h8l4 4v14H7V3Zm8 0v5h4M10 12h6M10 16h6" />
          </svg>
          <span>我的保障</span>
        </button>
      </nav>

      <div v-if="enrollmentOpen" class="enrollment-layer" role="dialog" aria-modal="true">
        <header class="enrollment-header">
          <button aria-label="关闭投保流程" @click="closeEnrollment">×</button>
          <div>
            <span>{{ preQuoteConfirming ? "投保前确认" : "投保流程" }}</span>
            <strong>{{ selectedProduct?.name ?? "保障计划" }}</strong>
          </div>
          <small>{{ preQuoteConfirming ? "准备投保" : `${enrollmentStep}/4` }}</small>
        </header>
        <div v-if="!preQuoteConfirming" class="progress-track">
          <span v-for="stepNo in 4" :key="stepNo" :class="{ active: stepNo <= enrollmentStep }"></span>
        </div>
        <div v-else class="direct-entry-line"></div>

        <div class="enrollment-body">
          <section v-if="preQuoteConfirming" class="enrollment-step direct-enrollment-step">
            <p class="step">DIRECT ENROLLMENT</p>
            <h2>确认投保条件</h2>
            <p class="step-description">顾问已经为你选好方案，确认以下信息后即可测算保费并继续投保。</p>

            <div class="direct-product">
              <div>
                <span>顾问推荐</span>
                <h3>{{ selectedProduct?.name }}</h3>
                <p>{{ selectedProduct?.summary }}</p>
              </div>
              <em>已选择</em>
            </div>

            <div class="form-section direct-condition-form">
              <div class="form-section-title"><span>被保人情况</span><small>用于资格校验和保费测算</small></div>
              <label>
                与投保人关系
                <span class="relation-options">
                  <button
                    v-for="item in (['SELF', 'PARENT', 'SPOUSE', 'CHILD'] as Relationship[])"
                    :key="item"
                    type="button"
                    :class="{ active: relationship === item }"
                    @click="relationship = item"
                  >
                    {{ relationshipName(item) }}
                  </button>
                </span>
              </label>
              <label>
                被保人年龄
                <input v-model.number="insuredAge" type="number" min="0" max="120" inputmode="numeric" />
              </label>
              <div class="fixed-conditions">
                <span>常住地区：成都市</span><span>职业类别：普通职业</span>
              </div>
            </div>

            <div class="direct-entry-note">
              <span>AI</span>
              <p>以上信息已根据你的咨询自动带入，请确认无误。最终是否可投保及保费以本次系统测算结果为准。</p>
            </div>
            <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
            <button
              class="flow-primary"
              :disabled="businessLoading"
              @click="confirmDirectEnrollment"
            >
              {{ businessLoading ? "正在测算保费…" : "确认并开始投保" }} <span>→</span>
            </button>
          </section>

          <section v-else-if="enrollmentStep === 1" class="enrollment-step">
            <p class="step">STEP 01</p>
            <h2>填写投保信息</h2>
            <p class="step-description">请确认信息真实、准确，这些信息将写入投保订单。</p>
            <div class="quote-mini">
              <div><span>{{ selectedProduct?.name }}</span><small>{{ relationshipName(relationship) }} · {{ insuredAge }}岁</small></div>
              <strong>{{ quote ? money(quote.premium_cents) : "—" }}</strong>
            </div>

            <div class="form-section">
              <div class="form-section-title"><span>投保人</span><small>负责缴费和接收通知</small></div>
              <label>姓名<input v-model.trim="applicantName" autocomplete="name" placeholder="请输入真实姓名" /></label>
              <label>身份证号<input v-model.trim="applicantIdNo" maxlength="18" placeholder="请输入18位身份证号" /></label>
              <label>联系手机<input v-model.trim="contactMobile" inputmode="tel" maxlength="11" placeholder="请输入接收通知的手机号" /><small class="input-help">默认使用登录手机号，可修改</small></label>
            </div>
            <div class="form-section">
              <div class="form-section-title"><span>被保人</span><small>实际享受保障的人</small></div>
              <label>与投保人关系
                <select v-model="relationship">
                  <option value="SELF">本人</option><option value="PARENT">父母</option>
                  <option value="SPOUSE">配偶</option><option value="CHILD">子女</option>
                </select>
              </label>
              <label>姓名<input v-model.trim="insuredName" placeholder="请输入真实姓名" /></label>
              <label>身份证号<input v-model.trim="insuredIdNo" maxlength="18" placeholder="请输入18位身份证号" /></label>
            </div>
            <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
            <button class="flow-primary" @click="goToNotice">下一步：阅读投保须知 <span>→</span></button>
          </section>

          <section v-else-if="enrollmentStep === 2" class="enrollment-step notice-step">
            <p class="step">STEP 02</p>
            <h2>投保须知</h2>
            <p class="step-description">这些内容会影响承保和理赔，请完整阅读后再确认。</p>
            <div class="notice-document">
              <header><span>智保通投保须知</span><small>适用于 {{ selectedProduct?.name }}</small></header>
              <div>
                <h3>一、投保资格</h3>
                <p>被保人应符合产品约定的年龄、常住地区和职业范围。本次试算结果仅对当前填写信息有效。</p>
                <h3>二、如实告知</h3>
                <p>投保人应如实填写身份及健康相关信息。故意或因重大过失未履行如实告知义务，可能影响合同效力及理赔结果。</p>
                <h3>三、保障生效</h3>
                <p>支付成功并完成承保后生成电子保单，具体保障责任、等待期、免赔额和责任限额以电子保单及保险条款为准。</p>
                <h3>四、责任免除</h3>
                <p>既往症、违法行为、故意伤害以及条款明确列示的其他情形可能不在保障范围内，请重点阅读责任免除部分。</p>
                <h3>五、信息与退保</h3>
                <p>投保信息将用于承保、保全和服务处理。退保规则、可退金额及生效后的处理方式以保险条款和后续服务流程为准。</p>
              </div>
            </div>
            <label class="notice-check">
              <input v-model="noticeAccepted" type="checkbox" />
              <span>我已阅读并理解《投保须知》《保险条款》和《责任免除》</span>
            </label>
            <label class="notice-check">
              <input v-model="disclosureConfirmed" type="checkbox" />
              <span>我确认投保信息真实、完整，并同意进行电子投保</span>
            </label>
            <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
            <div class="flow-actions">
              <button class="flow-back" @click="setEnrollmentStep(1)">上一步</button>
              <button class="flow-primary" @click="goToConfirmation">同意并继续 <span>→</span></button>
            </div>
          </section>

          <section v-else-if="enrollmentStep === 3" class="enrollment-step confirmation-step">
            <p class="step">STEP 03</p>
            <h2>确认投保订单</h2>
            <p class="step-description">请最后核对保障方案和人员信息。</p>
            <div class="confirm-product">
              <span>保障方案</span>
              <h3>{{ selectedProduct?.name }}</h3>
              <p>{{ relationshipName(relationship) }} · {{ insuredAge }}岁 · 成都市 · 普通职业</p>
              <strong>{{ quote ? money(quote.premium_cents) : "—" }}<small>/年</small></strong>
            </div>
            <dl class="confirm-list">
              <div><dt>投保人</dt><dd>{{ applicantName }}</dd></div>
              <div><dt>投保人证件</dt><dd>{{ maskIdNo(applicantIdNo) }}</dd></div>
              <div><dt>被保人</dt><dd>{{ insuredName }}</dd></div>
              <div><dt>被保人证件</dt><dd>{{ maskIdNo(insuredIdNo) }}</dd></div>
              <div><dt>联系电话</dt><dd>{{ contactMobile }}</dd></div>
            </dl>
            <div class="confirmed-notice"><span>✓</span>已阅读并同意投保须知与责任免除</div>
            <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
            <div class="flow-actions">
              <button class="flow-back" @click="setEnrollmentStep(2)">上一步</button>
              <button class="flow-primary" :disabled="businessLoading" @click="confirmAndCreateOrder">
                {{ businessLoading ? "正在创建订单…" : `确认投保并支付 ${quote ? money(quote.premium_cents) : ""}` }}
              </button>
            </div>
          </section>

          <section v-else class="enrollment-step payment-step">
            <template v-if="!policy">
              <p class="step">STEP 04</p>
              <h2>安全支付</h2>
              <p class="step-description">确认支付后将立即完成承保并生成电子保单。</p>
              <div class="payment-amount">
                <span>应付金额</span>
                <strong>{{ order ? money(order.amount_cents) : quote ? money(quote.premium_cents) : "—" }}</strong>
                <small>{{ selectedProduct?.name ?? "智保通保障计划" }}</small>
              </div>
              <div class="payment-channel">
                <span class="channel-icon">¥</span>
                <div><strong>快捷支付</strong><small>支付过程由安全通道保护</small></div>
                <em>已选择</em>
              </div>
              <p v-if="order" class="payment-order">订单号 {{ order.order_no }}</p>
              <p v-if="error" class="error status-message" role="alert"><span>!</span>{{ error }}</p>
              <button
                class="flow-primary pay-button"
                :disabled="businessLoading || !payment"
                @click="completePayment"
              >
                {{ businessLoading ? "正在确认支付…" : payment ? "确认支付" : "正在准备支付…" }}
              </button>
            </template>

            <template v-else>
              <div class="success-symbol"><span>✓</span></div>
              <p class="step success-step">ENROLLMENT COMPLETED</p>
              <h2>投保成功</h2>
              <p class="step-description">电子保单已经生成，保障信息已保存到“我的保障”。</p>
              <div class="success-policy">
                <span>电子保单号</span><strong>{{ policy.policy_no }}</strong>
                <div><small>保障状态</small><em>{{ policyStatus(policy.status) }}</em></div>
                <div><small>保障期限</small><p>{{ policy.coverage_start }} 至 {{ policy.coverage_end }}</p></div>
              </div>
              <button class="flow-primary" @click="viewCoverageAfterPayment">查看我的保障 <span>→</span></button>
            </template>
          </section>
        </div>
      </div>

      <div
        v-if="openedPolicy"
        class="policy-document-layer"
        role="dialog"
        aria-modal="true"
        aria-label="电子保单"
        @click.self="closePolicyDocument"
      >
        <article class="policy-document">
          <header class="policy-document-header">
            <button type="button" aria-label="关闭电子保单" @click="closePolicyDocument">×</button>
            <div><span>智保通</span><small>电子保险凭证</small></div>
            <em>{{ policyStatus(openedPolicy.status) }}</em>
          </header>

          <section class="policy-document-hero">
            <p>POLICY CERTIFICATE</p>
            <h2>{{ openedPolicy.product_name }}</h2>
            <span>{{ openedPolicy.plan_name }}</span>
            <div><small>电子保单号</small><strong>{{ openedPolicy.policy_no }}</strong></div>
          </section>

          <section class="policy-document-body">
            <div class="document-section-title"><span>01</span><h3>合同信息</h3></div>
            <dl class="document-fields">
              <div><dt>保单状态</dt><dd class="active-status">{{ policyStatus(openedPolicy.status) }}</dd></div>
              <div><dt>订单编号</dt><dd>{{ openedPolicy.order_no }}</dd></div>
              <div><dt>签发时间</dt><dd>{{ dateTime(openedPolicy.issued_at) }}</dd></div>
              <div><dt>年度保费</dt><dd>{{ money(openedPolicy.premium_cents) }}</dd></div>
              <div class="wide-field"><dt>保障期限</dt><dd>{{ openedPolicy.coverage_start }} 至 {{ openedPolicy.coverage_end }}</dd></div>
            </dl>

            <div class="document-section-title"><span>02</span><h3>被保险人信息</h3></div>
            <dl class="document-fields">
              <div><dt>投保人</dt><dd>{{ openedPolicy.applicant.name ?? "—" }}</dd></div>
              <div><dt>投保人证件</dt><dd>{{ maskIdNo(openedPolicy.applicant.id_no ?? "") || "—" }}</dd></div>
              <div><dt>被保人</dt><dd>{{ openedPolicy.insured.name ?? "—" }}</dd></div>
              <div><dt>被保人证件</dt><dd>{{ maskIdNo(openedPolicy.insured.id_no ?? "") || "—" }}</dd></div>
            </dl>

            <div class="policy-document-note">
              <span>i</span>
              <p>本电子凭证记录本次承保结果。具体保障责任、责任免除、等待期及赔付限额以保险条款和投保须知为准。</p>
            </div>
            <div class="policy-seal"><span>智保通</span><small>电子承保凭证</small></div>
            <button type="button" class="document-print" @click="printPolicyDocument">打印 / 保存为 PDF</button>
          </section>
        </article>
      </div>
    </div>
  </main>
</template>
