"use client";

import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { AiInterpretPanel } from "@/components/AiInterpretPanel";
import { CtkpResultPageNav } from "@/components/CtkpResultPageNav";
import { CtkpCompactLoading, CtkpUnavailableResult } from "@/components/CtkpPublicStates";
import {
  TuViA4OverviewSection,
  TuViChartBoard,
  TuViChartTitleBar,
  TuViChuyenDeInsightPanel,
  TuViInterpretPanel,
  TuViInterpretFocusOverlay,
  TuViInterpretFollowUpChat,
  TuViInterpretTabStrip,
  type TuViInterpretAiDeepDive,
  type TuViInterpretNormalized,
  type TuViInterpretPanelSelectedPalace,
  type TuViInterpretPanelTabId,
  TuViPageShell,
  TuViDaiVanPanel,
  TuViNguyetVanPanel,
  TuViNhatVanPanel,
  TuViSummaryBar,
  TuViTieuVanPanel,
  TuViTopTabs,
  TuViLuanCungWorkspace,
  TuViLuanCungSeedAccordion,
  TuViReadingFeedbackSection,
  type TuViSummaryItem,
  type TuViTopTabKey,
} from "@/components/tu-vi";
import type { FortuneReading } from "@/lib/fortune";
import { TUVI_RESULT_KEY, type TuViStoredPayload } from "@/lib/tuvi";
import { parseTuViStoredPayload, viewingYearFromStoredPayload } from "@/lib/tu-vi-stored-payload";
import { buildA4OverviewInterpretation } from "@/lib/tuvi-a4-overview-interpretation";
import {
  buildTuViPremiumTeaserV1,
  tuViA4EligibleForPremiumTeaserV1,
} from "@/lib/tuvi-premium-teaser.v1";
import { resolveTuViPremiumDeepReportForUi } from "@/lib/tuvi-premium-report.v1";
import {
  buildTuViWealthInterpretTabBody,
  mapApiTuViToChartViewModel,
  splitPalaceBranchDisplay,
  type TuViChartViewModel,
} from "@/lib/tuvi-chart-view-model";
import { clampInterpretTabToPalace } from "@/lib/tuvi-palace-section-priority";
import { TUVI_PALACE_KEYS } from "@/lib/tuvi-palace-interpret-sections-config";
import { buildPalaceInterpretationView } from "@/lib/tuvi-palace-interpret-view-model";
import { parseTuViInterpretationContext, parseTuViInterpretationDebug, parseTuViInterpretationProse } from "@/lib/tuvi-interpretation-context";
import { isTuViInterpretationProseDebugPanelEnabled } from "@/lib/tuvi-interpretation-debug";
import { mapTuViFortuneFromPayload } from "@/lib/tuvi-fortune-view-model";
import { pickTuViLaSoHousesPayload } from "@/lib/tuvi-result-types";
import { getApiBaseUrl } from "@/lib/api";
import { isBetaPublicSurfaceEnabled } from "@/lib/betaVisibility";
import {
  flushTelemetryQueue,
  isSoftLaunchClientEnabled,
  queueSoftLaunchTelemetry,
} from "@/lib/soft-launch-client";
import {
  pickTuViFunnelPayload,
  TUVI_FUNNEL_EVENT,
  queueTuViFunnelTelemetry,
} from "@/lib/tuvi-funnel-telemetry.v1";
import {
  buildTuViChartSaveBody,
  postTuViChartSave,
  type TuViChatTranscriptTurnV1,
} from "@/lib/tuvi-chart-records-api";
import { saveTuViReading } from "@/lib/saved-tu-vi-readings-api";
import {
  getAccessToken,
  getMe,
  type WebAuthNormalizedError,
} from "@/lib/web-auth-client";
import {
  clearYearlyRevisitIntentFromSession,
  readYearlyRevisitIntentFromSession,
} from "@/lib/tuvi-yearly-revisit-intent.v1";
import {
  buildTuViPdfReportBody,
  postTuViPdfReport,
} from "@/lib/tuvi-pdf-report-api";
import type { TuViPdfExportPhase } from "@/lib/tuvi-pdf-export-labels";
import { tuViPdfExportButtonLabel } from "@/lib/tuvi-pdf-export-labels";

const calendarLabel: Record<string, string> = {
  solar: "Dương lịch",
  lunar: "Âm lịch",
};

type TuViKetQuaState =
  | { kind: "loading" }
  | { kind: "missing" }
  | { kind: "invalid" }
  | { kind: "ready"; data: TuViStoredPayload };

function vmSec(vm: TuViChartViewModel, key: string) {
  return vm.summarySections.find((s) => s.key === key);
}

const LOG_NS = "[tu-vi/ket-qua]";
const TUVI_KET_QUA_LOG_RESTORE = process.env.NODE_ENV === "development";

const TUVI_SAVED_IDS_LS = "ctkp_tuvi_saved_chart_ids_v1";
const TUVI_RESTORED_CHAT_SS = "ctkp_tuvi_restored_ai_chat";

function readSavedChartIdMap(): Record<string, boolean> {
  if (typeof localStorage === "undefined") return {};
  try {
    const raw = localStorage.getItem(TUVI_SAVED_IDS_LS);
    const o = raw ? (JSON.parse(raw) as unknown) : {};
    return o && typeof o === "object" && !Array.isArray(o) ? (o as Record<string, boolean>) : {};
  } catch {
    return {};
  }
}

function markChartSaved(chartResultId: string) {
  if (typeof localStorage === "undefined") return;
  try {
    const next = { ...readSavedChartIdMap(), [chartResultId]: true };
    localStorage.setItem(TUVI_SAVED_IDS_LS, JSON.stringify(next));
  } catch {
    /* ignore */
  }
}

function readRestoredAiChatOnce(): TuViChatTranscriptTurnV1[] {
  if (typeof sessionStorage === "undefined") return [];
  try {
    const raw = sessionStorage.getItem(TUVI_RESTORED_CHAT_SS);
    sessionStorage.removeItem(TUVI_RESTORED_CHAT_SS);
    if (!raw) return [];
    const parsed = JSON.parse(raw) as unknown;
    if (!Array.isArray(parsed)) return [];
    return parsed
      .filter((x): x is Record<string, unknown> => x !== null && typeof x === "object" && !Array.isArray(x))
      .map((x): TuViChatTranscriptTurnV1 => ({
        role: x.role === "assistant" ? "assistant" : "user",
        content: typeof x.content === "string" ? x.content : "",
        createdAt: typeof x.createdAt === "string" ? x.createdAt : new Date().toISOString(),
      }))
      .filter((m) => m.content.trim().length > 0 && (m.role === "user" || m.role === "assistant"));
  } catch {
    return [];
  }
}

function tuviFunnelErrorCode(e: unknown): string {
  if (e && typeof e === "object") {
    const w = e as WebAuthNormalizedError;
    if (typeof w.code === "string" && w.code.trim()) return w.code.trim().slice(0, 64);
    if (typeof w.status === "number") return `HTTP_${w.status}`;
  }
  if (e instanceof Error && e.message.trim()) return e.message.trim().slice(0, 64);
  return "unknown";
}

export default function TuViKetQuaPage() {
  const searchParams = useSearchParams();
  const rawSeedDebug =
    process.env.NODE_ENV !== "production" && searchParams.get("luanCungSeed") === "1";
  const proseDebugUi = isTuViInterpretationProseDebugPanelEnabled();
  const betaPublicSurface = isBetaPublicSurfaceEnabled();
  const journalHref = betaPublicSurface ? "/tu-vi/da-luu" : "/fortune/history";
  const journalLabelShort = betaPublicSurface ? "lá đã lưu" : "nhật ký";
  const journalLabelButton = betaPublicSurface ? "Lá đã lưu" : "Nhật ký";
  const [state, setState] = useState<TuViKetQuaState>({ kind: "loading" });
  const [topTab, setTopTab] = useState<TuViTopTabKey>("la-so");
  const [interpretTab, setInterpretTab] = useState<TuViInterpretPanelTabId>("overview");
  const [interpretOverlayOpen, setInterpretOverlayOpen] = useState(false);
  const [savedChartMap, setSavedChartMap] = useState<Record<string, boolean>>({});
  const [saveBusy, setSaveBusy] = useState(false);
  const [saveMessage, setSaveMessage] = useState<string | null>(null);
  /** `null` = đang kiểm tra phiên; chỉ hiện nút lưu tài khoản khi `true`. */
  const [accountSaveEligible, setAccountSaveEligible] = useState<boolean | null>(null);
  const [accountSaveBusy, setAccountSaveBusy] = useState(false);
  const [accountSaveMessage, setAccountSaveMessage] = useState<string | null>(null);
  const [accountSaveShowVerifyLink, setAccountSaveShowVerifyLink] = useState(false);
  const [chatTranscript, setChatTranscript] = useState<TuViChatTranscriptTurnV1[]>([]);
  const [restoredAiChat] = useState<TuViChatTranscriptTurnV1[]>(() => readRestoredAiChatOnce());
  const [pdfPhase, setPdfPhase] = useState<TuViPdfExportPhase>("idle");

  useEffect(() => {
    setSavedChartMap(readSavedChartIdMap());
  }, []);

  useEffect(() => {
    if (state.kind !== "ready") return;
    if (!isSoftLaunchClientEnabled()) return;
    queueSoftLaunchTelemetry({
      chartId: state.data.result.id,
      eventType: "chart_reading_opened",
      payload: { path: "/tu-vi/ket-qua" },
    });
  }, [state]);
  /** Cùng một ô cho tab Lá số và Luận cung — mặc định Mệnh. */
  const [selectedPalaceKey, setSelectedPalaceKey] = useState("menh");

  const scrollToChartBlock = useCallback(() => {
    if (typeof document === "undefined") return;
    if (topTab === "luan-cung") {
      document.getElementById("tuvi-luan-cung-root")?.scrollIntoView({ behavior: "smooth", block: "start" });
    } else {
      document.getElementById("tuvi-chart-board-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" });
    }
  }, [topTab]);

  const scrollToAiDeep = useCallback(() => {
    document.getElementById("tuvi-ai-deep")?.scrollIntoView({ behavior: "smooth", block: "start" });
  }, []);

  const interpretAiDeepDive = useMemo(
    (): TuViInterpretAiDeepDive => ({
      available: Boolean(getApiBaseUrl()),
      onRequest: scrollToAiDeep,
      unavailableHint:
        "Luận AI sâu chưa bật trên thiết bị này. Bạn vẫn có thể đọc luận từ engine và các mục tra cứu trên lá.",
    }),
    [scrollToAiDeep],
  );

  useEffect(() => {
    if (TUVI_KET_QUA_LOG_RESTORE && typeof window !== "undefined") {
      console.info(`${LOG_NS} client mount`);
    }
    queueMicrotask(() => {
      try {
        if (TUVI_KET_QUA_LOG_RESTORE) {
          console.info(`${LOG_NS} restore: read sessionStorage`);
        }
        const raw = sessionStorage.getItem(TUVI_RESULT_KEY);
        if (!raw) {
          if (TUVI_KET_QUA_LOG_RESTORE) {
            console.info(`${LOG_NS} restore: empty key → missing`);
          }
          setState({ kind: "missing" });
          return;
        }
        const parsed: unknown = JSON.parse(raw);
        const normalized = parseTuViStoredPayload(parsed);
        if (!normalized) {
          if (TUVI_KET_QUA_LOG_RESTORE) {
            console.warn(`${LOG_NS} restore: payload failed validation → invalid`);
          }
          setState({ kind: "invalid" });
          return;
        }
        if (TUVI_KET_QUA_LOG_RESTORE) {
          console.info(`${LOG_NS} restore: ready`);
        }
        setState({ kind: "ready", data: normalized });
      } catch (e) {
        if (TUVI_KET_QUA_LOG_RESTORE) {
          console.error(`${LOG_NS} restore: parse or IO failed`, e);
        }
        setState({ kind: "invalid" });
      }
    });
  }, []);

  const viewModelOrNull = useMemo((): TuViChartViewModel | null => {
    if (state.kind !== "ready") return null;
    return mapApiTuViToChartViewModel(state.data);
  }, [state]);

  const fortuneViewModel = useMemo(() => {
    if (state.kind !== "ready" || !viewModelOrNull) return null;
    return mapTuViFortuneFromPayload(state.data, viewModelOrNull);
  }, [state, viewModelOrNull]);

  const viewingYear = useMemo(
    () => viewingYearFromStoredPayload(state.kind === "ready" ? state.data : null),
    [state],
  );

  const [funnelAuth, setFunnelAuth] = useState<{ hasAccount: boolean; phoneVerified: boolean }>({
    hasAccount: false,
    phoneVerified: false,
  });

  useEffect(() => {
    if (state.kind !== "ready") {
      setFunnelAuth({ hasAccount: false, phoneVerified: false });
      return;
    }
    const token = getAccessToken();
    if (!token) {
      setFunnelAuth({ hasAccount: false, phoneVerified: false });
      return;
    }
    let cancelled = false;
    void getMe().then(
      ({ user }) => {
        if (!cancelled) {
          setFunnelAuth({ hasAccount: true, phoneVerified: user.phoneVerifiedAt != null });
        }
      },
      () => {
        if (!cancelled) setFunnelAuth({ hasAccount: true, phoneVerified: false });
      },
    );
    return () => {
      cancelled = true;
    };
  }, [state]);

  const funnelA4OverviewMeta = useMemo(() => {
    if (state.kind !== "ready" || !viewModelOrNull) return null;
    const seedInterpretProse = parseTuViInterpretationProse(state.data.result.interpretationProse);
    const housesForOverview = pickTuViLaSoHousesPayload(state.data.result);
    const a4Overview = buildA4OverviewInterpretation(
      {
        palaceProses: seedInterpretProse?.palaceProses ?? null,
        chartOverviewOnePage: seedInterpretProse?.chartOverviewOnePage ?? null,
      },
      housesForOverview,
    );
    if (!a4Overview) {
      return { chartId: state.data.result.id, source: state.data.source, overviewRenderable: false };
    }
    const useServerOverviewBody = a4Overview.overviewSource === "server";
    const overviewRenderable =
      (useServerOverviewBody ? a4Overview.body.length >= 120 : true) &&
      (useServerOverviewBody || a4Overview.sections.length > 0);
    return {
      chartId: state.data.result.id,
      source: state.data.source,
      overviewRenderable: Boolean(overviewRenderable),
    };
  }, [state, viewModelOrNull]);

  const funnelResultSentForChart = useRef<string | null>(null);
  const funnelA4SentForChart = useRef<string | null>(null);
  const revisitReadingGenSentForChart = useRef<string | null>(null);

  useEffect(() => {
    if (!isSoftLaunchClientEnabled() || state.kind !== "ready" || !viewModelOrNull) return;
    const chartId = state.data.result.id;
    if (funnelResultSentForChart.current === chartId) return;
    funnelResultSentForChart.current = chartId;
    queueTuViFunnelTelemetry({
      eventType: TUVI_FUNNEL_EVENT.result_view,
      chartId,
      payload: {
        source: state.data.source,
        viewingYearSolar: viewingYear,
        hasAccount: funnelAuth.hasAccount,
        phoneVerified: funnelAuth.phoneVerified,
        path: "/tu-vi/ket-qua",
      },
    });
  }, [state, viewModelOrNull, viewingYear, funnelAuth]);

  useEffect(() => {
    if (!isSoftLaunchClientEnabled() || state.kind !== "ready") return;
    const intent = readYearlyRevisitIntentFromSession();
    if (!intent || intent.targetYear !== viewingYear) return;
    const chartId = state.data.result.id;
    if (revisitReadingGenSentForChart.current === chartId) return;
    revisitReadingGenSentForChart.current = chartId;
    queueTuViFunnelTelemetry({
      eventType: TUVI_FUNNEL_EVENT.revisit_reading_generated,
      chartId,
      payload: pickTuViFunnelPayload({
        source: "yearly_revisit_intent",
        path: "/tu-vi/ket-qua",
        viewingYearSolar: viewingYear,
        recommendedViewingYear: intent.targetYear,
      }),
    });
    clearYearlyRevisitIntentFromSession();
  }, [state, viewingYear]);

  useEffect(() => {
    if (!isSoftLaunchClientEnabled() || !funnelA4OverviewMeta?.overviewRenderable || !viewModelOrNull) return;
    const chartId = funnelA4OverviewMeta.chartId;
    if (funnelA4SentForChart.current === chartId) return;
    funnelA4SentForChart.current = chartId;
    queueTuViFunnelTelemetry({
      eventType: TUVI_FUNNEL_EVENT.a4_overview_view,
      chartId,
      payload: {
        source: funnelA4OverviewMeta.source,
        viewingYearSolar: viewingYear,
        hasAccount: funnelAuth.hasAccount,
        phoneVerified: funnelAuth.phoneVerified,
        path: "/tu-vi/ket-qua",
      },
    });
  }, [funnelA4OverviewMeta, viewModelOrNull, viewingYear, funnelAuth]);

  const handleSaveTuViChart = useCallback(async () => {
    if (state.kind !== "ready") return;
    if (!getApiBaseUrl()) {
      setSaveMessage("Chưa kết nối được dịch vụ lưu lá — kiểm tra cấu hình triển khai.");
      return;
    }
    if (isSoftLaunchClientEnabled()) {
      queueTuViFunnelTelemetry({
        eventType: TUVI_FUNNEL_EVENT.save_click,
        chartId: state.data.result.id,
        payload: {
          source: state.data.source,
          viewingYearSolar: viewingYear,
          saveKind: "server_chart",
          hasAccount: funnelAuth.hasAccount,
          phoneVerified: funnelAuth.phoneVerified,
        },
      });
    }
    setSaveBusy(true);
    setSaveMessage(null);
    try {
      const body = buildTuViChartSaveBody(state.data, chatTranscript);
      const r = await postTuViChartSave(body);
      markChartSaved(state.data.result.id);
      setSavedChartMap(readSavedChartIdMap());
      setSaveMessage(
        r.saved ? "Đã lưu lá số trên máy chủ." : "Lá này đã có trong danh sách đã lưu — mở «Lá số đã lưu» để xem lại.",
      );
      if (isSoftLaunchClientEnabled()) {
        queueTuViFunnelTelemetry({
          eventType: TUVI_FUNNEL_EVENT.save_success,
          chartId: state.data.result.id,
          payload: {
            source: state.data.source,
            viewingYearSolar: viewingYear,
            saveKind: "server_chart",
            hasAccount: funnelAuth.hasAccount,
            phoneVerified: funnelAuth.phoneVerified,
          },
        });
      }
    } catch (e) {
      if (isSoftLaunchClientEnabled()) {
        queueTuViFunnelTelemetry({
          eventType: TUVI_FUNNEL_EVENT.save_failed,
          chartId: state.data.result.id,
          payload: {
            source: state.data.source,
            viewingYearSolar: viewingYear,
            saveKind: "server_chart",
            errorCode: tuviFunnelErrorCode(e),
          },
        });
      }
      setSaveMessage(e instanceof Error ? e.message : String(e));
    } finally {
      setSaveBusy(false);
    }
  }, [state, chatTranscript, viewingYear, funnelAuth]);

  useEffect(() => {
    if (state.kind !== "ready") return;
    let cancelled = false;
    setAccountSaveEligible(null);
    queueMicrotask(() => {
      void (async () => {
        const token = getAccessToken();
        if (!token) {
          if (!cancelled) setAccountSaveEligible(false);
          return;
        }
        try {
          const { user } = await getMe();
          if (!cancelled) setAccountSaveEligible(!user.requiresPhoneVerification);
        } catch {
          if (!cancelled) setAccountSaveEligible(false);
        }
      })();
    });
    return () => {
      cancelled = true;
    };
  }, [state]);

  const handleSaveTuViReadingToAccount = useCallback(async () => {
    if (state.kind !== "ready") return;
    if (!getApiBaseUrl()) {
      setAccountSaveMessage("Chưa kết nối được API — kiểm tra cấu hình triển khai.");
      return;
    }
    if (isSoftLaunchClientEnabled()) {
      queueTuViFunnelTelemetry({
        eventType: TUVI_FUNNEL_EVENT.save_click,
        chartId: state.data.result.id,
        payload: {
          source: state.data.source,
          viewingYearSolar: viewingYear,
          saveKind: "account_reading",
          hasAccount: funnelAuth.hasAccount,
          phoneVerified: funnelAuth.phoneVerified,
        },
      });
    }
    setAccountSaveBusy(true);
    setAccountSaveMessage(null);
    setAccountSaveShowVerifyLink(false);
    try {
      const pack = buildTuViChartSaveBody(state.data, chatTranscript);
      const r = state.data.result;
      const sub = r.subject;
      const saved = await saveTuViReading({
        title: r.reading.title.trim() || undefined,
        personName: sub.name,
        birthDate: sub.birthDate,
        birthTime: sub.birthTime,
        gender: sub.gender,
        inputJson: pack,
        resultJson: state.data,
      });
      setAccountSaveMessage("Đã lưu vào tài khoản — xem trong «Tài khoản» → «Lá số đã lưu».");
      if (isSoftLaunchClientEnabled()) {
        queueTuViFunnelTelemetry({
          eventType: TUVI_FUNNEL_EVENT.save_success,
          chartId: state.data.result.id,
          payload: {
            source: state.data.source,
            viewingYearSolar: viewingYear,
            saveKind: "account_reading",
            savedReadingId: saved.id,
            hasAccount: true,
            phoneVerified: funnelAuth.phoneVerified,
          },
        });
      }
    } catch (e: unknown) {
      if (isSoftLaunchClientEnabled()) {
        queueTuViFunnelTelemetry({
          eventType: TUVI_FUNNEL_EVENT.save_failed,
          chartId: state.data.result.id,
          payload: {
            source: state.data.source,
            viewingYearSolar: viewingYear,
            saveKind: "account_reading",
            errorCode: tuviFunnelErrorCode(e),
            hasAccount: funnelAuth.hasAccount,
            phoneVerified: funnelAuth.phoneVerified,
          },
        });
      }
      const w = e as WebAuthNormalizedError;
      if (w.status === 403 && w.code === "PHONE_VERIFICATION_REQUIRED") {
        setAccountSaveMessage("Cần xác minh số điện thoại trước khi lưu vào tài khoản.");
        setAccountSaveShowVerifyLink(true);
        setAccountSaveEligible(false);
      } else {
        setAccountSaveMessage(w.message || "Không lưu vào tài khoản được.");
      }
    } finally {
      setAccountSaveBusy(false);
    }
  }, [state, chatTranscript, viewingYear, funnelAuth]);

  const handleExportTuViPdf = useCallback(async () => {
    if (state.kind !== "ready") return;
    if (!getApiBaseUrl()) return;
    setPdfPhase("loading");
    try {
      const body = buildTuViPdfReportBody(state.data.result);
      const blob = await postTuViPdfReport(body);
      if (isSoftLaunchClientEnabled()) {
        queueSoftLaunchTelemetry({
          chartId: state.data.result.id,
          eventType: "pdf_export_success",
          payload: { approxBytes: blob.size },
        });
        void flushTelemetryQueue();
      }
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      const rawName = state.data.result.subject.name.trim();
      const safe =
        rawName.replace(/[^\p{L}\p{N}\-_]+/gu, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 48) ||
        "tu-vi";
      a.href = url;
      a.download = `${safe}-${state.data.result.id.slice(0, 8)}.pdf`;
      a.rel = "noopener";
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      setPdfPhase("success");
      window.setTimeout(() => setPdfPhase("idle"), 2200);
    } catch (e) {
      if (isSoftLaunchClientEnabled()) {
        queueSoftLaunchTelemetry({
          chartId: state.data.result.id,
          eventType: "pdf_export_error",
          payload: { message: e instanceof Error ? e.message.slice(0, 200) : "unknown" },
        });
        void flushTelemetryQueue();
      }
      setPdfPhase("error");
      window.setTimeout(() => setPdfPhase("idle"), 4200);
    }
  }, [state]);

  useEffect(() => {
    if (!viewModelOrNull) return;
    if (!viewModelOrNull.palaces.some((p) => p.key === selectedPalaceKey)) {
      queueMicrotask(() => setSelectedPalaceKey("menh"));
    }
  }, [viewModelOrNull, selectedPalaceKey]);

  useEffect(() => {
    if (!viewModelOrNull) return;
    const key = viewModelOrNull.palaces.some((p) => p.key === selectedPalaceKey) ? selectedPalaceKey : "menh";
    setInterpretTab((tab) => clampInterpretTabToPalace(key, tab));
  }, [selectedPalaceKey, viewModelOrNull]);

  useEffect(() => {
    setInterpretOverlayOpen(false);
  }, [topTab]);

  /** Mobile (<lg): vào tab Luận cung hoặc đổi ô thì cuộn tới khối luận cung. */
  useEffect(() => {
    if (topTab !== "luan-cung") return;
    let cancelled = false;
    const id1 = window.requestAnimationFrame(() => {
      window.requestAnimationFrame(() => {
        if (cancelled) return;
        if (!window.matchMedia("(max-width: 1023px)").matches) return;
        document.getElementById("tuvi-luan-cung-root")?.scrollIntoView({
          block: "start",
          behavior: "auto",
        });
      });
    });
    return () => {
      cancelled = true;
      cancelAnimationFrame(id1);
    };
  }, [topTab, selectedPalaceKey]);

  if (state.kind === "loading") {
    return (
      <main className="ctkp-result-canvas min-h-[100dvh]">
        <CtkpCompactLoading embedded title="Đang mở lá số…" subtitle="Khôi phục kết quả đã lưu trên trình duyệt." />
      </main>
    );
  }

  if (state.kind === "missing") {
    return (
      <main className="ctkp-result-canvas min-h-[100dvh]">
        <div className="mx-auto max-w-[760px] px-5 py-16 sm:px-8 sm:py-20">
          <CtkpUnavailableResult
            embedded
            title="Chưa có lá số để mở"
            body="Trang này chỉ mở được sau khi bạn ghi canh khắc sinh ở bước trước. Nếu là lần đầu, hoặc trình duyệt đã xóa nhớ, mời quay lại để ghi lại."
            primaryHref="/tu-vi"
            primaryLabel="Về ghi lá số"
            secondaryHref={journalHref}
            secondaryLabel={journalLabelButton}
          />
        </div>
      </main>
    );
  }

  if (state.kind === "invalid") {
    return (
      <main className="ctkp-result-canvas min-h-[100dvh]">
        <div className="mx-auto max-w-[760px] px-5 py-16 sm:px-8 sm:py-20">
          <CtkpUnavailableResult
            embedded
            title="Không đọc được kết quả"
            body={
              betaPublicSurface ?
                "Dấu lá số trong trình duyệt không đủ hoặc không còn khớp cách đọc hiện tại. Thử mở lại từ lá đã lưu, hoặc ghi một lá mới."
              : "Dấu lá số trong trình duyệt không đủ hoặc không còn khớp cách đọc hiện tại. Thử mở lại từ nhật ký, hoặc ghi một lá mới."
            }
            primaryHref="/tu-vi"
            primaryLabel="Ghi lá số mới"
            secondaryHref={journalHref}
            secondaryLabel={journalLabelButton}
          />
        </div>
      </main>
    );
  }

  const { data } = state;
  const { source, ctkp_history_warning_v1 } = data;
  const generationPartial = data.result.ctkp_generation_partial_v1;
  if (!viewModelOrNull) {
    return (
      <main className="ctkp-result-canvas min-h-[100dvh]">
        <div className="mx-auto max-w-[760px] px-5 py-16 sm:px-8 sm:py-20">
          <CtkpUnavailableResult
            embedded
            title="Không đọc được kết quả"
            body={
              betaPublicSurface ?
                "Không dựng được bản xem từ dữ liệu đã lưu. Thử mở lại từ lá đã lưu hoặc ghi lá mới."
              : "Không dựng được bản xem từ dữ liệu đã lưu. Thử mở lại từ nhật ký hoặc ghi lá mới."
            }
            primaryHref="/tu-vi"
            primaryLabel="Ghi lá số mới"
            secondaryHref={journalHref}
            secondaryLabel={journalLabelButton}
          />
        </div>
      </main>
    );
  }
  const vm = viewModelOrNull;
  /** Luận cung: chỉ cần `interpretationProse` — không phụ thuộc `interpretationContext` cho luồng chính. */
  const seedInterpretContext = parseTuViInterpretationContext(data.result.interpretationContext);
  const seedInterpretProse = parseTuViInterpretationProse(data.result.interpretationProse);
  const housesForOverview = pickTuViLaSoHousesPayload(data.result);
  const a4Overview = buildA4OverviewInterpretation(
    {
      palaceProses: seedInterpretProse?.palaceProses ?? null,
      chartOverviewOnePage: seedInterpretProse?.chartOverviewOnePage ?? null,
    },
    housesForOverview,
  );
  const premiumTeaser =
    a4Overview &&
    tuViA4EligibleForPremiumTeaserV1({
      wordCount: a4Overview.wordCount,
      overviewSource: a4Overview.overviewSource,
      body: a4Overview.body,
      sectionsCount: a4Overview.sections.length,
    }) ?
      buildTuViPremiumTeaserV1({
        viewingYearSolar: viewingYear,
        chartOverviewOnePage: seedInterpretProse?.chartOverviewOnePage ?? null,
      })
    : null;
  const premiumDeepReport = resolveTuViPremiumDeepReportForUi(data.result as Record<string, unknown>);
  const useServerOverviewBody = a4Overview?.overviewSource === "server";
  const interpretationDebugBlock = parseTuViInterpretationDebug(data.result.interpretationDebug);
  const bodyPalaceKeyAccordion = vm.palaces.find((p) => p.bodyMarker)?.key ?? null;
  const pf = vm.profile;
  const readingTitle = vmSec(vm, "reading-title")?.content ?? "—";
  const receivedLabel = vmSec(vm, "received-at")?.content ?? "—";
  const textSource = vmSec(vm, "text-source")?.content as FortuneReading["text_source"] | undefined;
  const starPlacementBlurb = vmSec(vm, "star-placement-phase1")?.content;
  const canhKhacChiTiet = vmSec(vm, "canh-khac-chi-tiet")?.content;
  const prepSummary = vmSec(vm, "prep-summary")?.content;
  const openingProvenance = vmSec(vm, "opening-provenance")?.content ?? "—";
  const readingOpening = vmSec(vm, "reading-opening")?.content ?? "";
  const tongQuan = vmSec(vm, "reading-tong-quan")?.content;
  const tinhCach = vmSec(vm, "reading-tinh-cach")?.content;
  const congViec = vmSec(vm, "reading-cong-viec")?.content;
  const tinhCam = vmSec(vm, "reading-tinh-cam")?.content;
  const loiKhuyenProse = vmSec(vm, "reading-loi-khuyen")?.content?.trim() ?? "";
  const versesBlock = vmSec(vm, "reading-verses")?.content ?? "";
  const versesSafe = versesBlock
    ? versesBlock.split("\n").map((l) => l.replace(/^\d+\.\s*/, "").trim())
    : [];
  const snapshotWarn = vmSec(vm, "snapshot-warning")?.content;
  const interpretTraceWarn = vmSec(vm, "interpret-trace-warning")?.content;
  const interpretTraceText = vmSec(vm, "interpret-trace")?.content ?? "";
  const keySignalsBlock = vmSec(vm, "prep-key-signals")?.content ?? "";
  const keySignalsSafe = keySignalsBlock
    ? keySignalsBlock.split("\n").map((l) => l.replace(/^\d+\.\s*/, "").trim()).filter(Boolean)
    : [];
  const tensionsBlock = vmSec(vm, "prep-tensions")?.content ?? "";
  const tensionsSafe = tensionsBlock
    ? tensionsBlock.split("\n").map((l) => l.replace(/^•\s*/, "").trim()).filter(Boolean)
    : [];
  const placementHintsBlock = vmSec(vm, "db-placement-hints")?.content ?? "";
  const placementHintLines = placementHintsBlock
    ? placementHintsBlock.split("\n\n").map((block) => {
        const lines = block.split("\n");
        const head = lines[0] ?? "";
        const hint = lines.slice(1).join("\n") || head;
        const starPalace = head.split(" · ");
        return {
          starLabel: starPalace[0] ?? "",
          palaceLabel: starPalace[1] ?? "",
          hint,
        };
      })
    : [];
  const starShelfSections = vm.summarySections.filter((s) => s.key.startsWith("star-shelf-"));
  const showSignalsBlock = keySignalsSafe.length > 0;
  const showStarsShelf = starShelfSections.length > 0;
  const dbLookupMeta = vmSec(vm, "db-lookup-meta")?.content ?? "";
  const menhPalaceVm = vm.palaces.find((p) => p.key === "menh");
  const mingQuickDisplay = (() => {
    const b = menhPalaceVm ? splitPalaceBranchDisplay(menhPalaceVm.branch) : { han: "", vi: "—" };
    return (b.han ? `${b.han} ${b.vi}` : b.vi).trim() || "—";
  })();
  const menhNameNote = vmSec(vm, "banner-menh-theme")?.content;
  const thanNameNote = vmSec(vm, "banner-than-theme")?.content;
  const cucDisplay = pf.cucText ?? "—";
  const menhChuDisplay = pf.menhChuText ?? "—";
  const thanChuDisplay = pf.thanChuText ?? "—";
  const centerAgeLine = pf.ageText?.trim() || "—";
  const chartSummaryItems: TuViSummaryItem[] = [
    { label: "Tuổi", value: centerAgeLine, hint: pf.lunarYearText },
    { label: "Mệnh", value: mingQuickDisplay, hint: menhNameNote ?? pf.menhText },
    { label: "Cục", value: cucDisplay, hint: pf.cucText },
    { label: "Mệnh chủ", value: menhChuDisplay, hint: menhNameNote },
    { label: "Thân chủ", value: thanChuDisplay, hint: thanNameNote },
    { label: "Âm Dương", value: pf.amDuongText ?? "—", hint: pf.genderText },
  ];
  const wealthTabBody = buildTuViWealthInterpretTabBody(vm);
  const calendarModeVm = vmSec(vm, "form-calendar-mode")?.content ?? "solar";
  const birthYmdVm = vmSec(vm, "form-birth-ymd")?.content ?? "";
  const formLunarNote = vmSec(vm, "form-lunar-note")?.content;
  const bannerMenhNote = vmSec(vm, "banner-menh-note")?.content;
  const bannerThanNote = vmSec(vm, "banner-than-note")?.content;

  const palaceOrderIndex = new Map<string, number>();
  vm.palaces.forEach((p, i) => palaceOrderIndex.set(p.key, i + 1));

  const interpretNormalized: TuViInterpretNormalized = {
    overview: {
      opening: readingOpening,
      tongQuan,
      tinhCach,
      loiKhuyen: loiKhuyenProse || undefined,
      verses: versesSafe.length ? versesSafe : undefined,
    },
    career: congViec?.trim() ?? "",
    wealth: wealthTabBody,
    love: tinhCam?.trim() ?? "",
  };

  const resolvedPalaceKey = vm.palaces.some((p) => p.key === selectedPalaceKey) ? selectedPalaceKey : "menh";
  const palaceInterpretation = buildPalaceInterpretationView(vm, resolvedPalaceKey, interpretNormalized);
  const selectedPalacePanel: TuViInterpretPanelSelectedPalace = (() => {
    const p = vm.palaces.find((x) => x.key === resolvedPalaceKey) ?? vm.palaces.find((x) => x.key === "menh")!;
    return {
      key: p.key,
      nameVi: p.name,
      indexOneBased: palaceOrderIndex.get(p.key) ?? 1,
    };
  })();

  const luanCungTabEnabled = vm.tabs.find((t) => t.key === "luan-cung")?.enabled ?? false;

  const alerts = (
    <>
      {ctkp_history_warning_v1?.message_vi ? (
        <p
          role="status"
          className="mt-4 rounded-lg border border-amber-800/40 bg-amber-950/25 px-4 py-3 font-sans text-[13px] leading-relaxed text-amber-100/90"
        >
          {ctkp_history_warning_v1.message_vi}
        </p>
      ) : null}
      {generationPartial?.message_vi ? (
        <p
          role="status"
          className="mt-3 rounded-lg border border-sky-800/40 bg-sky-950/25 px-4 py-3 font-sans text-[13px] leading-relaxed text-sky-100/90"
        >
          {generationPartial.message_vi}
        </p>
      ) : null}
      {snapshotWarn ? (
        <p
          role="note"
          className="mt-3 rounded-lg border border-stone-700/45 bg-[#0c0b0a]/55 px-4 py-3 font-sans text-[12px] leading-relaxed text-stone-400"
        >
          {snapshotWarn}
        </p>
      ) : null}
      {interpretTraceWarn ? (
        <p
          role="note"
          className="mt-3 rounded-lg border border-stone-700/45 bg-[#0c0b0a]/55 px-4 py-3 font-sans text-[12px] leading-relaxed text-stone-400"
        >
          {interpretTraceWarn}
        </p>
      ) : null}
    </>
  );

  return (
    <TuViPageShell>
      <CtkpResultPageNav
        backHref="/tu-vi"
        backLabel="← quay lại"
        journalLink={{ href: journalHref, label: journalLabelShort }}
      />
      {alerts}

      <TuViTopTabs tabs={vm.tabs} active={topTab} onChange={setTopTab} className="mt-4" />

      {getApiBaseUrl() ?
        <div className="mx-auto mt-3 flex max-w-3xl flex-wrap items-center justify-end gap-2 px-2 sm:px-3 lg:max-w-4xl">
          {source === "api" ?
            <Link
              href="/tu-vi/da-luu"
              className="font-sans text-[11px] text-[#d4af37]/85 underline-offset-4 hover:text-[#f0e6bc] hover:underline"
            >
              Lá số đã lưu
            </Link>
          : null}
          <button
            type="button"
            disabled={pdfPhase === "loading"}
            onClick={() => void handleExportTuViPdf()}
            className="rounded-lg border border-stone-600/45 bg-[#06080f]/70 px-3 py-1.5 font-ctkp-serif text-[10px] uppercase tracking-[0.14em] text-stone-200 transition-colors hover:border-[#d4af37]/35 hover:text-[#f5e6c8] disabled:cursor-not-allowed disabled:opacity-45"
          >
            {tuViPdfExportButtonLabel(pdfPhase)}
          </button>
          {source === "api" ?
            <button
              type="button"
              disabled={saveBusy || !getApiBaseUrl()}
              onClick={() => void handleSaveTuViChart()}
              className="rounded-lg border border-[#d4af37]/32 bg-[#d4af37]/10 px-3 py-1.5 font-ctkp-serif text-[10px] uppercase tracking-[0.14em] text-[#f5ebd6] transition-colors hover:bg-[#d4af37]/18 disabled:cursor-not-allowed disabled:opacity-45"
            >
              {savedChartMap[data.result.id] ? "Đã lưu" : saveBusy ? "Đang lưu…" : "Lưu lá số"}
            </button>
          : null}
          {accountSaveEligible ?
            <button
              type="button"
              disabled={accountSaveBusy || !getApiBaseUrl()}
              onClick={() => void handleSaveTuViReadingToAccount()}
              className="rounded-lg border border-emerald-700/40 bg-emerald-950/35 px-3 py-1.5 font-ctkp-serif text-[10px] uppercase tracking-[0.14em] text-emerald-100/90 transition-colors hover:bg-emerald-950/50 disabled:cursor-not-allowed disabled:opacity-45"
            >
              {accountSaveBusy ? "Đang lưu…" : "Lưu vào tài khoản"}
            </button>
          : null}
        </div>
      : null}

      {saveMessage ?
        <p className="mx-auto mt-2 max-w-3xl px-2 font-sans text-[11px] leading-relaxed text-slate-500 sm:px-3 lg:max-w-4xl">
          {saveMessage}
        </p>
      : null}
      {accountSaveMessage || accountSaveShowVerifyLink ?
        <div className="mx-auto mt-2 max-w-3xl px-2 font-sans text-[11px] leading-relaxed text-emerald-200/70 sm:px-3 lg:max-w-4xl">
          {accountSaveMessage ?
            <p>{accountSaveMessage}</p>
          : null}
          {accountSaveShowVerifyLink ?
            <p className="mt-1">
              <Link
                href={`/verify-phone?next=${encodeURIComponent("/tu-vi/ket-qua")}`}
                className="text-[#d4af37]/90 underline-offset-2 hover:underline"
              >
                Tới trang xác minh số điện thoại
              </Link>
            </p>
          : null}
        </div>
      : null}

      <div
        id="ctkp-tuvi-top-panel"
        role="tabpanel"
        aria-labelledby={`ctkp-tuvi-top-tab-${topTab}`}
        className={`mt-4 transition-opacity duration-300 ease-out ${
          topTab === "dai-van" ||
          topTab === "tieu-van" ||
          topTab === "nguyet-van" ||
          topTab === "nhat-van" ||
          topTab === "chuyen-de" ?
            "min-h-[min(62vh,620px)]"
          : ""
        }`}
      >
        {(topTab === "la-so" || topTab === "luan-cung") && (
          <div className="mx-auto max-w-[1200px] xl:max-w-[1320px]">
            <TuViChartTitleBar
              title={readingTitle}
              onDeepDive={scrollToAiDeep}
              className="mt-0"
              journalLink={{ href: journalHref, label: journalLabelButton }}
              kicker={topTab === "luan-cung" ? "Luận cung · theo ô" : undefined}
              subtitle={
                topTab === "luan-cung" ?
                  "Chọn cung ở thiên bàn thu nhỏ, bấm mục luận để đọc từng phần. Tab Lá số: thiên bàn đầy đủ."
                : "Bấm mục luận dưới thiên bàn để đọc từng phần tập trung. Chọn ô trên lưới để đổi cung (đồng bộ với tab Luận cung)."
              }
            />

            <TuViSummaryBar className="mt-3 hidden lg:block" items={chartSummaryItems} />

            {topTab === "la-so" ?
              <div className="mt-3 space-y-3 lg:hidden">
                <div className="flex flex-col gap-3 rounded-lg border border-[#d4af37]/12 bg-[#060912]/55 px-4 py-3 font-sans text-[11px] leading-relaxed sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
                  <p className="text-center sm:text-left">
                    <span className="text-[#e8d9a8]/92">Niêm ấn</span>{" "}
                    <span className="tabular-nums text-white/88">{receivedLabel}</span>
                    {textSource ?
                      <>
                        <span className="mx-2 text-slate-600" aria-hidden>
                          |
                        </span>
                        <span className="text-slate-400">
                          Nguồn luận:{" "}
                          <span className="text-slate-300">
                            {textSource === "rule_interpret_v1"
                              ? "quy tắc + tra cứu"
                              : textSource === "db_lookup_v1"
                                ? "tra cứu tham chiếu"
                                : "engine nội bộ"}
                          </span>
                        </span>
                      </>
                    : null}
                  </p>
                  {prepSummary ?
                    <p className="border-t border-[#d4af37]/10 pt-3 text-center text-slate-500 sm:border-t-0 sm:pt-0 sm:text-right">
                      {prepSummary}
                    </p>
                  : null}
                </div>

                {(source !== "api" || starPlacementBlurb) ?
                  <div className="flex flex-col gap-3 sm:flex-row sm:gap-3">
                    {source !== "api" ?
                      <p className="flex-1 rounded-lg border border-amber-800/35 bg-amber-950/20 px-3 py-2.5 font-sans text-[11px] leading-relaxed text-amber-100/88">
                        <span className="font-medium text-amber-200/95">Đang xem ngoại tuyến.</span> Một số ô có thể chưa đồng bộ với bản đầy đủ trên máy chủ.
                      </p>
                    : null}
                    {starPlacementBlurb ?
                      <p className="flex-1 rounded-lg border border-[#d4af37]/14 bg-[#0a1020]/5 px-3 py-2.5 font-sans text-[11px] leading-relaxed">
                        <span className="font-ctkp-serif text-[10px] uppercase tracking-[0.18em] text-[#d4af37]/75">
                          An sao · bước 1
                        </span>
                        <span className="mt-1 block text-slate-300">{starPlacementBlurb}</span>
                      </p>
                    : null}
                  </div>
                : null}
              </div>
            : topTab === "luan-cung" && !luanCungTabEnabled ?
              <div
                role="status"
                className="mt-3 rounded-lg border border-amber-800/35 bg-amber-950/18 px-4 py-3 font-sans text-[12px] leading-relaxed text-amber-100/88"
              >
                <span className="font-medium text-amber-200/95">Luận cung (phạm vi hiện tại).</span> Lá trong phiên này
                chưa có lưới đủ 12 cung hoặc luận có cấu trúc đầy đủ — phần luận theo ô vẫn chạy trên dữ liệu đã có và tra
                cứu kèm theo.
              </div>
            : null}

            <div className="mt-4 space-y-4">
              {topTab === "la-so" ?
                <>
                  <section
                    id="tuvi-chart-board-anchor"
                    className="scroll-mt-6 min-w-0 overflow-x-auto overflow-y-visible lg:overflow-x-visible"
                    aria-labelledby="tuvi-chart-board-heading"
                  >
                    <TuViChartBoard
                      className="w-full"
                      palaces={vm.palaces}
                      profile={pf}
                      centerChartTitle="TỬ VI NAM PHÁI"
                      viewingYear={viewingYear}
                      centerMenhDisplay={pf.menhText ?? mingQuickDisplay}
                      centerAgeLine={centerAgeLine}
                      centerTagline={undefined}
                      summaryItems={chartSummaryItems}
                      palaceOrderIndex={palaceOrderIndex}
                      selectedPalaceKey={resolvedPalaceKey}
                      onSelectPalace={setSelectedPalaceKey}
                      arenaSubtitle={undefined}
                      footnote={undefined}
                    />
                  </section>
                  <TuViA4OverviewSection
                    className="mt-4 w-full min-w-0"
                    funnelTelemetry={
                      isSoftLaunchClientEnabled() ?
                        { chartId: data.result.id, viewingYearSolar: viewingYear }
                      : null
                    }
                    softLaunchReading={
                      isSoftLaunchClientEnabled() ? { chartId: data.result.id } : null
                    }
                    overviewPalaces={TUVI_PALACE_KEYS.map((k) => {
                      const p = vm.palaces.find((x) => x.key === k);
                      return { key: k, name: p?.name ?? k };
                    })}
                    serverOverview={
                      useServerOverviewBody && a4Overview ?
                        {
                          body: a4Overview.body,
                          paragraphRoles: a4Overview.overviewParagraphRoles ?? [],
                          ...(a4Overview.layeredSections?.length ? { layeredSections: a4Overview.layeredSections } : {}),
                          ...(a4Overview.interpretationSignals?.length ?
                            { interpretationSignals: a4Overview.interpretationSignals }
                          : {}),
                          ...(a4Overview.strategicRecommendationsA4?.bullets?.length ?
                            {
                              strategicRecommendationsA4: a4Overview.strategicRecommendationsA4,
                              ...(a4Overview.strategicRecommendationsBlockPlain ?
                                { strategicRecommendationsBlockPlain: a4Overview.strategicRecommendationsBlockPlain }
                              : {}),
                            }
                          : {}),
                        }
                      : null
                    }
                    composedSections={useServerOverviewBody ? null : (a4Overview?.sections ?? null)}
                    composedFooter={useServerOverviewBody ? null : (a4Overview?.footer ?? null)}
                    body={null}
                    overviewPipelineDebug={
                      a4Overview ?
                        {
                          overviewSource: a4Overview.overviewSource,
                          semanticLayerApplied: a4Overview.semanticLayerApplied,
                          semanticReplacementCount: a4Overview.semanticReplacementCount,
                          semanticReplacementsSample: a4Overview.semanticReplacementsSample,
                        }
                      : null
                    }
                    fallbackMessage="Chưa có đủ dữ liệu để tạo luận giải khái quát."
                    premiumTeaser={premiumTeaser}
                    premiumDeepReport={premiumTeaser ? premiumDeepReport : null}
                    onContinueToDetailedAnalysis={() => setInterpretOverlayOpen(true)}
                  />
                  <TuViInterpretTabStrip
                    palaceKey={resolvedPalaceKey}
                    active={interpretTab}
                    onSelect={(id) => {
                      setInterpretTab(id);
                      setInterpretOverlayOpen(true);
                    }}
                    className="px-0.5"
                  />
                </>
              : (
                <TuViLuanCungWorkspace
                  palaces={vm.palaces}
                  palaceOrderIndex={palaceOrderIndex}
                  selectedPalaceKey={resolvedPalaceKey}
                  onSelectPalace={setSelectedPalaceKey}
                  centerHint={pf.fullName.trim() || undefined}
                >
                  <TuViInterpretTabStrip
                    palaceKey={resolvedPalaceKey}
                    active={interpretTab}
                    onSelect={(id) => {
                      setInterpretTab(id);
                      setInterpretOverlayOpen(true);
                    }}
                    className="px-0.5"
                  />
                  <TuViLuanCungSeedAccordion
                    className="mt-1"
                    palaceProses={seedInterpretProse?.palaceProses ?? null}
                    palaceRows={seedInterpretContext?.palaceInterpretations ?? null}
                    proseDebugUi={proseDebugUi}
                    rawSeedDebug={rawSeedDebug}
                    interpretationDebugBlock={interpretationDebugBlock}
                    selectedPalaceKey={resolvedPalaceKey}
                    onSelectPalace={setSelectedPalaceKey}
                    bodyPalaceKey={bodyPalaceKeyAccordion}
                  />
                </TuViLuanCungWorkspace>
              )}
            </div>

            <TuViInterpretFocusOverlay
              open={interpretOverlayOpen}
              onClose={() => setInterpretOverlayOpen(false)}
              onShowChart={scrollToChartBlock}
              headerExtra={
                <TuViInterpretTabStrip
                  size="compact"
                  palaceKey={resolvedPalaceKey}
                  active={interpretTab}
                  onSelect={setInterpretTab}
                  className="px-0.5"
                />
              }
            >
              <div className="mx-auto w-full max-w-[52rem]">
                <TuViInterpretPanel
                  chrome="minimal"
                  hideTabBar
                  layout="workspace"
                  className="w-full"
                  interpretation={palaceInterpretation}
                  selectedPalace={selectedPalacePanel}
                  activeTab={interpretTab}
                  onTabChange={setInterpretTab}
                  onNavigatePalace={(key) => vm.palaces.some((p) => p.key === key) && setSelectedPalaceKey(key)}
                  aiDeepDive={interpretAiDeepDive}
                  footer={
                    <p className="border-t border-[#d4af37]/10 pt-4 text-center font-sans text-[11px] leading-relaxed text-slate-500">
                      «Luận sâu» trên hàng tiêu đề cũng cuộn xuống phụ lục tùy chọn cuối trang.
                    </p>
                  }
                />
              </div>
            </TuViInterpretFocusOverlay>

            {topTab === "la-so" ?
                <details className="group mt-5 rounded-xl border border-[#d4af37]/12 bg-[rgba(5,8,20,0.45)] px-4 py-3 sm:px-5">
                  <summary className="ctkp-tuvi-text-title-soft cursor-pointer font-ctkp-serif text-[13px] tracking-[0.04em]">
                    Hồ sơ kỹ thuật &amp; căn cứ đã ghi
                  </summary>
                  <div className="mt-5 space-y-8 border-t border-stone-800/40 pt-5">
                  <dl className="grid gap-8 sm:grid-cols-2">
                    <div>
                      <dt className="ctkp-tuvi-text-muted font-ctkp-serif text-[10px] uppercase tracking-[0.14em]">
                        Họ và tên
                      </dt>
                      <dd className="ctkp-tuvi-text-body mt-1.5 font-ctkp-serif text-base">{pf.fullName}</dd>
                    </div>
                    <div>
                      <dt className="ctkp-tuvi-text-muted font-ctkp-serif text-[10px] uppercase tracking-[0.14em]">
                        Giới tính
                      </dt>
                      <dd className="ctkp-tuvi-text-body mt-1.5 font-sans text-sm">{pf.genderText}</dd>
                    </div>
                    <div>
                      <dt className="ctkp-tuvi-text-muted font-ctkp-serif text-[10px] uppercase tracking-[0.14em]">
                        Ngày sinh
                      </dt>
                      <dd className="ctkp-tuvi-text-body mt-1.5 font-sans text-sm">{pf.birthDateText}</dd>
                      <dd className="ctkp-tuvi-text-muted mt-1 font-sans text-xs">
                        {calendarLabel[calendarModeVm] ?? calendarModeVm} · {birthYmdVm}
                      </dd>
                      {formLunarNote ?
                        <dd className="ctkp-tuvi-text-muted mt-2 max-w-prose font-sans text-xs leading-relaxed">
                          {formLunarNote}
                        </dd>
                      : null}
                    </div>
                    <div>
                      <dt className="ctkp-tuvi-text-muted font-ctkp-serif text-[10px] uppercase tracking-[0.14em]">
                        Giờ sinh
                      </dt>
                      <dd className="ctkp-tuvi-text-body mt-1.5 font-ctkp-serif text-base">{pf.birthHourText}</dd>
                    </div>
                  </dl>

                  {canhKhacChiTiet ?
                    <div>
                      <h3 className="ctkp-tuvi-text-title-soft font-ctkp-serif text-[11px] uppercase tracking-[0.12em]">
                        Mốc lịch &amp; tứ trụ (engine)
                      </h3>
                      <p className="ctkp-tuvi-text-body mt-2 whitespace-pre-wrap font-sans text-xs leading-relaxed opacity-90">
                        {canhKhacChiTiet}
                      </p>
                    </div>
                  : null}

                  {bannerMenhNote || bannerThanNote ?
                    <div className="ctkp-tuvi-text-body space-y-3 font-sans text-xs opacity-90">
                      {bannerMenhNote ?
                        <p>
                          Mệnh — {bannerMenhNote}
                        </p>
                      : null}
                      {bannerThanNote ?
                        <p>
                          Thân — {bannerThanNote}
                        </p>
                      : null}
                    </div>
                  : null}

                  <p className="font-ctkp-serif text-[11px] leading-relaxed text-stone-600">{openingProvenance}</p>

                  {placementHintLines.length > 0 ?
                    <div>
                      <h3 className="ctkp-tuvi-text-title-soft font-ctkp-serif text-[10px] uppercase tracking-[0.2em]">
                        Gợi sao – cung (DB)
                      </h3>
                      <ul className="mt-3 space-y-3">
                        {placementHintLines.map((row, i) => (
                          <li
                            key={`${row.starLabel}-${row.palaceLabel}-${i}`}
                            className="border-t border-stone-800/35 pt-2 first:border-t-0 first:pt-0"
                          >
                            <p className="font-ctkp-serif text-[11px]">
                              <span className="ctkp-tuvi-text-title-soft">{row.starLabel}</span>{" "}
                              <span className="ctkp-tuvi-text-muted">· {row.palaceLabel}</span>
                            </p>
                            <p className="ctkp-tuvi-text-body mt-1 font-sans text-xs leading-relaxed opacity-90">
                              {row.hint}
                            </p>
                          </li>
                        ))}
                      </ul>
                    </div>
                  : null}

                  {showSignalsBlock ?
                    <div>
                      <h3 className="ctkp-tuvi-text-title-soft font-ctkp-serif text-[10px] uppercase tracking-[0.2em]">
                        Tín hiệu từ mốc
                      </h3>
                      <ol className="ctkp-tuvi-text-body mt-3 list-decimal space-y-2 pl-5 text-sm">
                        {keySignalsSafe.map((line, i) => (
                          <li key={i}>{line}</li>
                        ))}
                      </ol>
                      {tensionsSafe.length > 0 ?
                        <ul className="ctkp-tuvi-text-muted mt-4 list-none space-y-2 text-sm">
                          {tensionsSafe.map((t, i) => (
                            <li key={i}>{t}</li>
                          ))}
                        </ul>
                      : null}
                    </div>
                  : null}

                  {showStarsShelf ?
                    <div>
                      <h3 className="ctkp-tuvi-text-title-soft font-ctkp-serif text-[10px] uppercase tracking-[0.2em]">
                        Ý lược sao (kho)
                      </h3>
                      <ul className="mt-3 space-y-4">
                        {starShelfSections.map((s) => (
                          <li key={s.key} className="border-b border-stone-800/35 pb-3 last:border-0">
                            <p className="ctkp-tuvi-text-title-soft font-ctkp-serif text-sm">{s.title}</p>
                            <p className="ctkp-tuvi-text-body mt-1 whitespace-pre-wrap font-sans text-xs opacity-90">
                              {s.content}
                            </p>
                          </li>
                        ))}
                      </ul>
                    </div>
                  : null}

                  {dbLookupMeta ?
                    <p className="ctkp-tuvi-text-muted whitespace-pre-wrap font-sans text-xs">{dbLookupMeta}</p>
                  : null}

                  {interpretTraceText.trim() ?
                    <details className="ctkp-tuvi-glass rounded-lg px-3 py-2">
                      <summary className="ctkp-tuvi-text-muted cursor-pointer font-ctkp-serif text-[12px]">
                        interpret_trace
                      </summary>
                      <pre className="ctkp-tuvi-text-muted mt-3 overflow-x-auto whitespace-pre-wrap font-mono text-[10px] leading-relaxed">
                        {interpretTraceText}
                      </pre>
                    </details>
                  : null}
                  </div>
                </details>
              : null}
          </div>
        )}

        {topTab === "dai-van" && fortuneViewModel ?
          <TuViDaiVanPanel items={fortuneViewModel.daiVan} onGoLaSo={() => setTopTab("la-so")} />
        : null}
        {topTab === "tieu-van" && fortuneViewModel ?
          <TuViTieuVanPanel view={fortuneViewModel.tieuVan} onGoLaSo={() => setTopTab("la-so")} />
        : null}
        {topTab === "nguyet-van" && fortuneViewModel ?
          <TuViNguyetVanPanel months={fortuneViewModel.nguyetVan} monthlyPeriodProse={fortuneViewModel.monthlyPeriodProse} />
        : null}
        {topTab === "nhat-van" && fortuneViewModel ?
          <TuViNhatVanPanel view={fortuneViewModel.nhatVan} onGoLaSo={() => setTopTab("la-so")} />
        : null}
        {topTab === "chuyen-de" ?
          <div className="animate-[ctkp-fade-in-slow_0.45s_ease-out_both]">
            <TuViChuyenDeInsightPanel
              topics={fortuneViewModel?.insightProse?.topics}
              personalizedAdvice={fortuneViewModel?.personalizedAdvice ?? undefined}
              onOpenLuancung={() => setTopTab("luan-cung")}
              onOpenLaSo={() => setTopTab("la-so")}
            />
          </div>
        : null}
      </div>

      {(topTab === "la-so" || topTab === "luan-cung" || topTab === "chuyen-de") && (
        <div
          id="tuvi-ai-deep"
          className="mx-auto mt-10 max-w-3xl scroll-mt-10 sm:scroll-mt-12 lg:max-w-4xl"
        >
          <AiInterpretPanel kind="tuvi" resultPayload={data} appendixMode />
          <TuViInterpretFollowUpChat
            chartIdentityKey={data.result.id}
            chartSnapshot={data.result as Record<string, unknown>}
            calendarMode={data.calendarMode}
            viewingYearSolar={viewingYear}
            restoredAiChatMessages={restoredAiChat.length > 0 ? restoredAiChat : undefined}
            onTranscriptChange={setChatTranscript}
          />
        </div>
      )}

      <div className="mx-auto mt-10 max-w-3xl px-2 sm:px-3 lg:max-w-4xl">
        <TuViReadingFeedbackSection
          chartId={data.result.id}
          source="tu_vi_ket_qua"
          className="w-full"
        />
      </div>

      <footer className="mt-10 sm:mt-12" aria-label="Lưu ý">
        <p className="ctkp-tuvi-text-muted text-center font-ctkp-serif text-xs leading-[1.85]">
          Đây không thay cho lời thầy hay thầy thuốc. Cổ Thư Kỳ Phổ dựng khung từ engine và tra cứu để bạn tự ngẫm; bước đi
          sau cùng vẫn do chính bạn chọn.
        </p>
      </footer>
    </TuViPageShell>
  );
}
