/* ============================================================================
 * 서명전에 — iOS Result Screen
 * ========================================================================== */

/* ────────────────────────────────────────────────────────────────────────────
 * 핵심 조건 포맷터
 *   백엔드가 추출한 원본 텍스트(예: "금 사억오천만원정", "₩450,000,000",
 *   "2026년 5월 30일 시작 / 2028년 5월 29일 종료")를 화면용으로 라운딩해서
 *   표시. 원본 데이터는 변형하지 않고, 렌더링 직전에만 변환한다.
 * ──────────────────────────────────────────────────────────────────────────── */

/* ────────────────────────────────────────────────────────────────────────────
 * 하이라이트 마커 변환
 *   LLM 이 verdict_title (또는 다른 자유 텍스트) 안에서 핵심 키워드를
 *   `[[K]]강조할 부분[[/K]]` 마커로 감싸서 보낸다. 프런트는 이 마커를
 *   <mark className="hl"> 로 변환해서 렌더한다. 마커가 없으면 그대로.
 *
 *   왜 [[K]] 마커인가:
 *     - <mark> 태그를 LLM 이 직접 출력하면 dangerouslySetInnerHTML 이 필요해
 *       XSS 위험 + sanitize 비용. 마커 방식은 평문이라 안전.
 *     - [[ ]] 는 일반 한국어/계약서 텍스트에 자연 등장 거의 없음 → 충돌 적음.
 * ──────────────────────────────────────────────────────────────────────────── */
function _renderHighlighted(text) {
  if (text == null || text === "") return text;
  const parts = String(text).split(/(\[\[K\]\][^\[]*?\[\[\/K\]\])/g);
  return parts.map((part, i) => {
    if (part.startsWith("[[K]]") && part.endsWith("[[/K]]")) {
      return <mark key={i} className="hl">{part.slice(5, -6)}</mark>;
    }
    return part;
  });
}

const _KF_MONEY_LABELS = new Set([
  "보증금/전세금", "보증금", "전세금",
  "계약금", "중도금", "잔금",
  "월 차임", "차임", "월세", "월 임대료", "임대료",
  "관리비",
]);

/* ────────────────────────────────────────────────────────────────────────────
 * PDF 내보내기용 작은 유틸들 — ResultScreen 의 exportPdf 핸들러가 사용.
 *   - _bufToBase64: ArrayBuffer → prefix 없는 base64 (saveBase64Data 스펙)
 *   - _parseDispositionFilename: Content-Disposition 헤더에서 파일명 추출
 *   - _stampNow: 파일명 폴백용 타임스탬프
 * ──────────────────────────────────────────────────────────────────────────── */
function _bufToBase64(buf) {
  const bytes = new Uint8Array(buf);
  // 대용량 PDF 일 수도 있으므로 fromCharCode 에 한 번에 큰 배열을 넘기지 않는다.
  let binary = "";
  const chunk = 0x8000;
  for (let i = 0; i < bytes.length; i += chunk) {
    binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
  }
  return btoa(binary);
}

function _parseDispositionFilename(header) {
  if (!header) return null;
  // RFC 5987: filename*=UTF-8''<percent-encoded>
  const star = header.match(/filename\*=UTF-8''([^;]+)/i);
  if (star && star[1]) {
    try { return decodeURIComponent(star[1].trim()); } catch (_) { /* fall through */ }
  }
  const plain = header.match(/filename="?([^";]+)"?/i);
  if (plain && plain[1]) return plain[1].trim();
  return null;
}

function _stampNow() {
  const d = new Date();
  const pad = (n) => String(n).padStart(2, "0");
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}`;
}

function _parseKoreanNumerals(raw) {
  if (!raw) return null;
  const s = String(raw).replace(/\s+/g, "").replace(/[정원()]/g, "");
  if (!s) return null;
  const digit = { "영":0,"공":0,"일":1,"이":2,"삼":3,"사":4,"오":5,"육":6,"칠":7,"팔":8,"구":9 };
  const unit1 = { "십":10,"백":100,"천":1000 };
  const unit2 = { "만":10000,"억":100000000,"조":1000000000000 };
  let total = 0, block = 0, subBlock = 0, lastDigit = 0;
  for (const ch of s) {
    if (ch in digit) {
      lastDigit = digit[ch];
    } else if (ch in unit1) {
      subBlock += (lastDigit || 1) * unit1[ch];
      lastDigit = 0;
    } else if (ch in unit2) {
      let chunk = block + subBlock + lastDigit;
      if (chunk === 0) chunk = 1;
      total += chunk * unit2[ch];
      block = 0; subBlock = 0; lastDigit = 0;
    } else {
      return null;
    }
  }
  total += block + subBlock + lastDigit;
  return total > 0 ? total : null;
}

function _toKoreanCurrency(n) {
  if (!Number.isFinite(n) || n <= 0) return null;
  const 억 = Math.floor(n / 100000000);
  const rem = n % 100000000;
  const 만 = Math.floor(rem / 10000);
  const 원 = rem % 10000;
  const parts = [];
  if (억 > 0) parts.push(`${억.toLocaleString("ko-KR")}억`);
  if (만 > 0) parts.push(`${만.toLocaleString("ko-KR")}만원`);
  if (원 > 0) parts.push(`${원.toLocaleString("ko-KR")}원`);
  if (parts.length === 0) return `${n.toLocaleString("ko-KR")}원`;
  if (parts.length === 1 && 억 > 0 && 만 === 0 && 원 === 0) return `${억}억원`;
  return parts.join(" ");
}

function _extractKRW(raw) {
  if (!raw) return null;
  // ₩ / 금 + 숫자 + 원 형태
  const digitMatch = String(raw).match(/(?:₩|금)?\s*([\d]{1,3}(?:,[\d]{3})+|[\d]+)\s*원?/);
  if (digitMatch) {
    const n = parseInt(digitMatch[1].replace(/,/g, ""), 10);
    if (!isNaN(n) && n >= 10000) return n;
  }
  // 한글 수사 형태 (사억오천만원정)
  const koreanMatch = String(raw).match(/금?\s*([영공일이삼사오육칠팔구십백천만억조\s]+)\s*원/);
  if (koreanMatch) {
    const n = _parseKoreanNumerals(koreanMatch[1]);
    if (n) return n;
  }
  return null;
}

function _formatCompactDate(text) {
  return String(text).replace(
    /(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/g,
    (_, y, m, d) => `${y}.${parseInt(m, 10)}.${parseInt(d, 10)}`
  );
}

function _formatMoneyValue(raw) {
  if (!raw) return raw;
  const parts = String(raw).split("·").map(s => s.trim());
  const amountPart = parts[0];
  const extraParts = parts.slice(1);
  const n = _extractKRW(amountPart);
  const formattedAmount = n ? _toKoreanCurrency(n) : amountPart;
  if (extraParts.length === 0) return formattedAmount;
  const extras = extraParts.map(_formatCompactDate).join(" · ");
  return `${formattedAmount} · ${extras}`;
}

function _formatLeasePeriod(raw) {
  if (!raw) return raw;
  const m = String(raw).match(
    /(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일[\s\S]+?(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/
  );
  if (!m) return _formatCompactDate(raw);
  const [, sy, smo, sd, ey, emo, ed] = m;
  const sY = parseInt(sy, 10), sM = parseInt(smo, 10), sD = parseInt(sd, 10);
  const eY = parseInt(ey, 10), eM = parseInt(emo, 10), eD = parseInt(ed, 10);
  // 전세/임대차 관행: 종료일이 시작일의 "전날"이면 만 N개월로 간주 (end-date-inclusive).
  //   2026.5.30 ~ 2028.5.29 → 24개월
  //   2026.5.1  ~ 2028.4.30 → 24개월
  // 종료일에 하루를 더해 "다음 계약 시작일"로 환산한 뒤 월 수를 계산한다.
  // Date 생성자가 월 롤오버(4/31 → 5/1)를 자동 처리한다.
  const endPlus1 = new Date(eY, eM - 1, eD + 1);
  const pY = endPlus1.getFullYear();
  const pM = endPlus1.getMonth() + 1;
  const pD = endPlus1.getDate();
  const months = (pY - sY) * 12 + (pM - sM) + (pD >= sD ? 0 : -1);
  const start = `${sY}.${sM}.${sD}`;
  const end = `${eY}.${eM}.${eD}`;
  if (months >= 1) return `${start} ~ ${end} · ${months}개월`;
  return `${start} ~ ${end}`;
}

function formatKeyFactValue(label, raw) {
  if (!raw) return raw;
  if (label === "계약기간") return _formatLeasePeriod(raw);
  if (_KF_MONEY_LABELS.has(label)) return _formatMoneyValue(raw);
  // 소재지·임대부분 등은 그대로
  return raw;
}

window.formatKeyFactValue = formatKeyFactValue;

function ResultScreen({ scenario = "jeonse", analysis = null, onBack, onOpenIssue, onOpenPreview }) {
  /* 실제 분석 결과가 있으면 그것을 사용, 없으면 시나리오 목업으로 폴백 */
  const data = useMemo(() => {
    if (analysis && typeof window.toResultData === "function") {
      const mapped = window.toResultData(analysis);
      if (mapped) return mapped;
    }
    return window.SCENARIOS[scenario] || window.SCENARIOS.jeonse;
  }, [analysis, scenario]);

  const scrollRef = useRef(null);
  const [scrolled, setScrolled] = useState(false);
  const [pdfBusy, setPdfBusy] = useState(false);

  /**
   * "PDF 리포트로 저장하기" 핸들러.
   *
   * 흐름:
   *   1) `POST /pdf/export` 로 분석 객체를 보내 PDF 바이트를 받는다.
   *      - 저장된 분석이면 `GET /account/history/{id}/pdf` 도 쓸 수 있지만,
   *        방금 분석한(=아직 저장 전) 결과도 지원하려면 POST 모드가 필요하다.
   *   2) 응답을 base64 로 인코딩한다.
   *   3) `TossAdapter.data.saveBase64(...)` 호출.
   *      - 앱인토스: 네이티브 파일 저장 대화상자 (saveBase64Data)
   *      - 웹: a[download] 로 브라우저 다운로드 (TossAdapter 웹 폴백)
   *   4) 실패 시 사용자에게 즉시 피드백.
   *
   * 주의:
   *   - 분석 결과가 없을 땐(=목업 시나리오 화면) 버튼이 아무 일도 안 하도록 둔다.
   *     (원래 placeholder 였고, 실제 분석이 있어야만 유의미한 문서가 나옴)
   *   - Supabase 로그인 토큰이 필요하다(엔드포인트가 require_account).
   */
  const exportPdf = async () => {
    if (!analysis) {
      alert("분석 결과가 없어 저장할 수 없어요.");
      return;
    }
    if (pdfBusy) return;
    setPdfBusy(true);
    try {
      // 인증 토큰 자동 주입 + 기본 Accept 헤더 세팅.
      // apiFetch 가 아직 안 올라왔다면(드문 로드 순서 이슈) 기본 fetch 로 폴백.
      const doFetch = (typeof window.apiFetch === "function") ? window.apiFetch : fetch;
      const res = await doFetch("/pdf/export", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(analysis),
      });
      if (!res.ok) {
        let msg = "PDF 를 만드는 데 실패했어요.";
        try { const j = await res.json(); if (j?.detail) msg = j.detail; } catch (_) { /* noop */ }
        if (res.status === 401) msg = "로그인이 필요해요. 다시 로그인해 주세요.";
        throw new Error(msg);
      }
      const blob = await res.blob();
      // Blob → base64 (saveBase64Data 는 prefix 없는 base64 문자열을 받음)
      const buf = await blob.arrayBuffer();
      const b64 = _bufToBase64(buf);

      // 서버가 Content-Disposition 으로 보내준 파일명 사용, 없으면 폴백
      const fileName = _parseDispositionFilename(res.headers.get("Content-Disposition"))
        || `서명전에_분석결과_${_stampNow()}.pdf`;

      const adapter = (typeof window !== "undefined" && window.TossAdapter) || null;
      if (!adapter?.data?.saveBase64) {
        throw new Error("파일 저장을 지원하지 않는 환경이에요.");
      }
      await adapter.data.saveBase64({
        data: b64,
        fileName,
        mimeType: "application/pdf",
      });
      // 앱인토스에서는 네이티브 성공 UI 가 뜨지만, 폴백 웹에서는 별도 토스트가 없어 살짝 알려준다.
      if (!adapter.isInAppsInToss || !adapter.isInAppsInToss()) {
        // noop — <a download> 자체로 충분히 인지 가능.
      }
    } catch (err) {
      console.error("[PDF export] failed", err);
      const adapter = (typeof window !== "undefined" && window.TossAdapter) || null;
      if (adapter?.TossPermissionError && err instanceof adapter.TossPermissionError) {
        alert("파일 저장 권한이 필요해요. 토스 앱에서 권한을 허용해 주세요.");
      } else {
        alert(err?.message || "PDF 저장 중 오류가 발생했어요.");
      }
    } finally {
      setPdfBusy(false);
    }
  };

  /* 헤드라인 어휘 + 색 톤은 legal_status 우선순위(void > unfair > negotiable) 로 결정.
     사용자가 "1건의 고위험 조항" 보다 "1건의 불공정 조항" 이 더 와닿는다고 함 —
     하단 legal_totals 의 어휘 체계와 헤드라인을 일치시켜서 인지 부담을 줄인다.
     색도 같이 통일: void=빨간(alert), unfair=노란(warn), negotiable=회색(safe).

     ⚠️ numeric_audits 의 status="mismatch" (보증금 합산 불일치 같은 계약서 자체
     모순) 는 사실 issue 보다 더 치명적 — Hero 카운트에 합산하고 톤도 alert
     로 트리거. 이전엔 "꼭 알아두세요" 보조 영역에만 표시되어 사용자가 헤드만
     봤을 때 "0건의 확인할 조항" 으로 안심시켜 버렸음.

     legal 분류가 모두 0인 (heuristic-only) 케이스는 risk severity 로 폴백. */
  const mismatchCount = Array.isArray(data.numeric_audits)
    ? data.numeric_audits.filter((na) => (na?.status || "").toLowerCase() === "mismatch").length
    : 0;
  /* 참고하면 좋은 항목 카운트 — missing_items + numeric_audits(status=warning).
     warning 인 numeric audits 는 결국 "법으로 보호되는 정보성 항목" 이라
     missing_items 와 같은 회색 섹션에서 함께 보여줌. hero 카운트도 합산. */
  const lawProtectedAuditCount = Array.isArray(data.numeric_audits)
    ? data.numeric_audits.filter((na) => (na?.status || "").toLowerCase() === "warning").length
    : 0;
  const missingCount =
    (Array.isArray(data.missing_items) ? data.missing_items.length : 0) + lawProtectedAuditCount;
  /* heroKind — Result hero 의 카운트/톤 결정.
     톤은 **라벨이 말하는 것과 색이 1:1 매칭** 되게 단순화. numericMismatch 는
     별도 "꼭 알아두세요" 섹션에 이미 표시되므로 hero tone 엘리베이션에 섞지
     않음. 홈 목록 historyItemSev 와도 동일 매핑.
       ① void                 → alert (빨강)
       ② unfair               → warn (주황)
       ③ negotiable           → safe (초록)
       ④ risk.high            → alert
       ⑤ risk.medium          → warn
       ⑥ numericMismatch 만 → warn
       ⑦ missingCount 만     → warn
       ⑧ 그 외                → safe

     parts: [{ count, label }, ...] — 법 위반 + 불공정 둘 다 > 0 이면 length=2. */
  const heroKind = (() => {
    const lt = data.legal_totals || {};
    const v = lt.void || 0;
    const u = lt.unfair || 0;
    const n = lt.negotiable || 0;
    if (v > 0 && u > 0) {
      return {
        parts: [
          { count: v, label: "법 위반 조항" },
          { count: u, label: "불공정 조항" },
        ],
        tone: "alert",
      };
    }
    if (v > 0) return { parts: [{ count: v, label: "법 위반 조항" }], tone: "alert" };
    if (u > 0) return { parts: [{ count: u, label: "불공정 조항" }], tone: "warn" };
    if (n > 0) return { parts: [{ count: n, label: "협상 대상" }], tone: "safe" };
    if ((data.risk_totals?.high || 0) > 0) {
      return { parts: [{ count: data.risk_totals.high, label: "고위험 조항" }], tone: "alert" };
    }
    if ((data.risk_totals?.medium || 0) > 0) {
      return { parts: [{ count: data.risk_totals.medium, label: "주의 조항" }], tone: "warn" };
    }
    if (mismatchCount > 0) {
      return { parts: [{ count: mismatchCount, label: "꼭 확인할 항목" }], tone: "warn" };
    }
    /* missing_items 만 있는 케이스 — "위험 조항 0건 + 체크 N건" 2-part 로
       표시하고 톤은 safe(초록). 실제 위험 조항은 0건이라 주황(warn) 으로
       올리면 과경고. "위험은 없고, 보완만 필요한 상태" 를 시각적으로 안심
       시그널(초록) + 숫자로 명확히 전달. 사용자 요청.
       "0건의 위험 조항 및 8건의 체크해야 할 항목" 형태. */
    if (missingCount > 0) {
      return {
        parts: [
          { count: 0, label: "위험 조항" },
          { count: missingCount, label: "참고하면 좋은 항목" },
        ],
        tone: "safe",
      };
    }
    return { parts: [{ count: 0, label: "확인할 조항" }], tone: "safe" };
  })();
  const sev = heroKind.tone; // hero 색 톤은 헤드라인 분류와 동일하게 가져간다 (위계 통일)
  /* safe 케이스에 res-hero--safe 를 꼭 붙여야 함. 안 붙이면 .res-hero__count-n
     기본 color 가 var(--brand)(파랑) 으로 fallback 되는데, 홈 썸네일의
     .recent__thumb--safe 는 var(--safe)(초록) 이라 "협상 대상" 이 홈에선 초록
     → 상세에선 파랑으로 보이는 색 엇갈림이 생김. 토큰 수준에서 safe=초록
     이 이미 정답이므로 hero 에도 똑같이 safe 클래스 적용. */
  const heroMod = sev === "alert" ? "res-hero--alert"
    : sev === "warn" ? "res-hero--warn"
    : sev === "safe" ? "res-hero--safe"
    : "";

  // 과거엔 히어로가 짙은 컬러 배경이어서 상태표시줄 글자를 흰색으로 덮어썼지만,
  // 지금은 히어로가 밝은 카드라 기본(검정) 상태표시줄 그대로 두면 가독성이 더 좋다.
  useEffect(() => {
    const island = document.querySelector(".iphone__status");
    if (!island) return;
    island.style.color = "";
  }, []);

  const contractTypeLabel = data.contract_type_label;
  const icon =
    data.contract_type === "employment" ? "briefcase"
    : data.contract_type === "freelancer" ? "briefcase"
    : scenario === "wolsae" ? "building"
    : "home-key";

  const sortedIssues = useMemo(() => {
    const order = { high: 0, medium: 1, low: 2 };
    return [...(data.issues || [])].sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));
  }, [data]);

  const redflagCount = Array.isArray(data.redflags) ? data.redflags.filter((r) => r?.detected).length : 0;
  const hasRedflags = Array.isArray(data.redflags) && data.redflags.length > 0;

  return (
    <div className="app">
      <div className={"navbar" + (scrolled ? " navbar--scrolled" : "")}>
        <button className="navbar__btn" onClick={onBack} style={{ marginLeft: 2 }}>
          <IOSIcon name="chev-left" size={22} weight={2.4} />
          <span>홈</span>
        </button>
        <div className="navbar__title" style={{ opacity: scrolled ? 1 : 0, transition: "opacity 200ms" }}>
          분석 결과
        </div>
        {/* 앱인토스 미니앱 정책 4(외부 링크 차단): 외부 공유 인텐트는 지원하지 않음.
            링크 복사(내부 공유)만 허용. TossAdapter.clipboard 로 앱인토스 ↔ 웹
            양쪽에서 동일 API 를 사용한다 — 앱인토스에서는 setClipboardText,
            웹에서는 navigator.clipboard(→ execCommand) 순으로 폴백. */}
        <button
          className="navbar__btn"
          style={{ marginRight: 6 }}
          onClick={async () => {
            const url = (typeof window !== "undefined" && window.location?.href) || "";
            const adapter = (typeof window !== "undefined" && window.TossAdapter) || null;
            try {
              if (adapter?.clipboard?.writeText) {
                await adapter.clipboard.writeText(url);
              } else if (navigator?.clipboard?.writeText) {
                // 어댑터가 아직 안 떴을 때만 직접 호출 (개발 중 로드 순서 이슈 대비)
                await navigator.clipboard.writeText(url);
              } else {
                alert("링크 복사를 지원하지 않는 환경이에요");
                return;
              }
              alert("분석 링크를 복사했어요");
            } catch (err) {
              if (adapter?.TossPermissionError && err instanceof adapter.TossPermissionError) {
                alert("클립보드 쓰기 권한이 필요해요");
              } else {
                alert("링크 복사를 지원하지 않는 환경이에요");
              }
            }
          }}
          aria-label="분석 링크 복사"
        >
          <IOSIcon name="share" size={21} />
        </button>
      </div>

      <div className="scroll scroll--plain" ref={scrollRef} onScroll={(e) => setScrolled(e.currentTarget.scrollTop > 20)}>
        {/* Hero */}
        <div className={"card res-hero " + heroMod} style={{ marginTop: 4 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 10 }}>
            <IOSIcon name={icon} size={16} />
            <span className="res-hero__type">{contractTypeLabel}</span>
          </div>
          {/* 단일 part: "3건의 협상 대상" 처럼 한 줄.
              두 part: "0건의 위험 조항, 7건의 체크해야 할 항목" 형태.
              ⚠️ 이전엔 가운데 " 및 " 분리자를 썼는데, 좁은 모바일 화면에서
              wrap 되면 두 번째 줄 맨 앞에 "및" 만 외롭게 남아 어색했음. 사용자
              요청: 콤마(,) 로 바꿔 첫 번째 part 끝에 붙이고, 두 번째 part 가
              자연스럽게 다음 줄로 흘러가도록. 숫자 크기는 두 part 동일 (56px).
          */}
          <div className="res-hero__count">
            {heroKind.parts.map((p, i) => {
              const isLast = i === heroKind.parts.length - 1;
              return (
                <span
                  key={i}
                  className={"res-hero__count-part" + (i > 0 ? " res-hero__count-part--secondary" : "")}
                >
                  <span className="res-hero__count-n tnum">{p.count}</span>
                  <span className="res-hero__count-unit">
                    건의 {p.label}{!isLast ? "," : ""}
                  </span>
                </span>
              );
            })}
          </div>
          <p className="res-hero__desc">{_renderHighlighted(data.verdict_title)}</p>

          {/* 앱인토스 미니앱 정책 5.2(AI 사용 고지): 생성형 AI가 만든 결과임을 명시. */}
          <div
            className="res-hero__ai-notice"
            style={{
              marginTop: 10,
              padding: "8px 12px",
              borderRadius: 10,
              background: "color-mix(in srgb, var(--brand) 8%, transparent)",
              color: "var(--label-secondary, #4e5968)",
              fontSize: 12,
              lineHeight: 1.5,
              display: "flex",
              alignItems: "flex-start",
              gap: 8,
            }}
            role="note"
            aria-label="AI 생성 결과 안내"
          >
            <span aria-hidden="true" style={{ fontSize: 13, flexShrink: 0 }}>🤖</span>
            <span>
              이 분석은 생성형 AI가 작성한 참고 의견이에요. 법적 효력은 없고, 중요한 결정 전에는 전문가 상담을 권장해요.
            </span>
          </div>

          {/* 법적 판단 바 — 실제 분석에서 감지 깊이가 한눈에 보이도록 */}
          {data.is_real && data.legal_totals && (
            <div className="res-hero__verdict">
              <div className="res-hero__verdict-item">
                <span className="res-hero__verdict-n tnum">{data.legal_totals.void || 0}</span>
                <span className="res-hero__verdict-label">법 위반</span>
              </div>
              <div className="res-hero__verdict-sep" />
              <div className="res-hero__verdict-item">
                <span className="res-hero__verdict-n tnum">{data.legal_totals.unfair || 0}</span>
                <span className="res-hero__verdict-label">불공정</span>
              </div>
              <div className="res-hero__verdict-sep" />
              <div className="res-hero__verdict-item">
                <span className="res-hero__verdict-n tnum">{data.legal_totals.negotiable || 0}</span>
                <span className="res-hero__verdict-label">협상 대상</span>
              </div>
            </div>
          )}

          {/* hero 하단 chips ("중 N건·경미 N건·1p·OCR N자") 는 사용자에게 의미가
              모호하고 (특히 OCR 글자수는 디버그성 정보) 시각적 노이즈만 남겨서 제거.
              중·경미 카운트는 res-stats 로 별도 표시되고, 페이지 수 / OCR 통계는
              필요하면 issue 시트 메타로 옮길 수 있음. */}
        </div>

        {/* Quick stats — 실제 분석은 법적 지위 분포로, 목업은 기존 스탯 그대로 */}
        <div className="res-stats">
          {data.is_real ? (
            <>
              <div className="res-stat">
                <div className="res-stat__val tnum">{data.issues.length}</div>
                <div className="res-stat__label">총 이슈</div>
              </div>
              <div className={"res-stat" + ((data.legal_totals?.void || 0) > 0 ? " res-stat--alert" : "")}>
                <div className="res-stat__val tnum">{data.legal_totals?.void || 0}</div>
                <div className="res-stat__label">법 위반</div>
              </div>
              <div className={"res-stat" + ((data.legal_totals?.unfair || 0) > 0 ? " res-stat--warn" : "")}>
                <div className="res-stat__val tnum">{data.legal_totals?.unfair || 0}</div>
                <div className="res-stat__label">불공정</div>
              </div>
            </>
          ) : (
            <>
              <div className="res-stat">
                <div className="res-stat__val tnum">{data.issues.length}</div>
                <div className="res-stat__label">총 이슈</div>
              </div>
              <div className="res-stat">
                <div className="res-stat__val tnum">{redflagCount}</div>
                <div className="res-stat__label">레드플래그</div>
              </div>
              <div className="res-stat">
                <div className="res-stat__val tnum">{data.key_facts.length}</div>
                <div className="res-stat__label">핵심 조건</div>
              </div>
            </>
          )}
        </div>

        {/* Key facts — 비어 있으면 아예 숨김 (실제 분석에서 추출 실패 케이스) */}
        {data.key_facts && data.key_facts.length > 0 && (
          <div className="inset-group">
            <div className="inset-group__header">핵심 조건</div>
            <div className="inset-group__list">
              {data.key_facts.map((kf, i) => (
                <div className="inset-row" key={i} style={{ cursor: "default" }}>
                  <span className="inset-row__label" style={{ fontSize: 15 }}>{kf.label}</span>
                  <span className="inset-row__value tnum" style={{
                    color: kf.meta === "위험" || kf.meta === "최저임금 미달" || kf.meta === "확인 필요" ? "var(--alert)" : "var(--label-secondary)",
                    fontWeight: 500, opacity: 1,
                    whiteSpace: "normal",
                    wordBreak: "keep-all",
                    overflowWrap: "anywhere",
                    textAlign: "right",
                    flex: "1 1 auto",
                    minWidth: 0,
                  }}>
                    {(() => {
                      const formatted = formatKeyFactValue(kf.label, kf.value);
                      // "금액 · 날짜 지급" 같은 합성 값은 두 줄로 분리 — 사용자가 헷갈림 방지.
                      if (typeof formatted === "string" && formatted.includes(" · ")) {
                        const [head, ...rest] = formatted.split(" · ");
                        const tail = rest.join(" · ");
                        return (
                          <>
                            <span>{head}</span>
                            <span style={{
                              display: "block",
                              fontSize: 12,
                              color: "var(--label-tertiary, #999)",
                              fontWeight: 400,
                              marginTop: 2,
                            }}>
                              {tail}
                            </span>
                          </>
                        );
                      }
                      return formatted;
                    })()}
                  </span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Issues */}
        <div className="section-h" style={{ marginTop: 8 }}>
          <h3 className="section-h__title">조항별 이슈</h3>
          <button className="section-h__link" onClick={onOpenPreview}>
            원문 보기
          </button>
        </div>

        <div className="issue-list">
          {sortedIssues.map((iss) => {
            /* sKey/sevLabel 은 legal_status 를 우선해서 결정한다. 이렇게 하면
               hero("1건의 불공정 조항", 노란) 와 카드 색이 일치해서 사용자가
               "왜 위쪽은 노란데 카드는 빨간이지?" 라고 느끼지 않는다.
               legal_status 가 비어 있을 때만 severity(high/medium/low) 로 폴백. */
            const lstat = iss.legal_status || "";
            const sKey = (
              lstat === "void"       ? "high" :
              lstat === "unfair"     ? "mid"  :
              lstat === "negotiable" ? "low"  :
              iss.severity === "high"   ? "high" :
              iss.severity === "medium" ? "mid"  : "low"
            );
            const sMod = "issue__sev--" + sKey;
            const iconName = sKey === "high" ? "exclam" : sKey === "mid" ? "warn-tri" : "info";
            /* sevLabel 도 legal_status 어휘로 통일 ("법 위반/불공정/협상 대상").
               legal 분류 없을 때만 severity 어휘 폴백. */
            const sevLabel = (
              lstat === "void"       ? "법 위반"   :
              lstat === "unfair"     ? "불공정"    :
              lstat === "negotiable" ? "협상 대상" :
              iss.severity === "high"   ? "고위험" :
              iss.severity === "medium" ? "주의"   : "경미"
            );
            const statusLabel =
              iss.legal_status === "void" ? "효력 없을 수도" :
              iss.legal_status === "unfair" ? "한쪽에 불리" :
              iss.legal_status === "negotiable" ? "조율할 부분" : "";
            const statusBadgeColor =
              iss.legal_status === "void" ? "red" :
              iss.legal_status === "unfair" ? "yellow" :
              iss.legal_status === "negotiable" ? "elephant" : null;
            // 카드 preview — 하이라이트 마커는 시트에서만 보이면 충분하고,
            // 카드에선 평문이 깔끔. 회귀: "[[K]]별도 협의 없이[[/K]]" 가 raw
            // 로 렌더되던 문제 (slice 가 마커 중간을 자르면 더 지저분해짐).
            // 먼저 모든 마커(`[[K]]...[[/K]]` 와 `**...**`) 를 벗긴 뒤 slice.
            const _strip = (s) => String(s || "")
              .replace(/\[\[K\]\]/g, "")
              .replace(/\[\[\/K\]\]/g, "")
              .replace(/\*\*([^*]+)\*\*/g, "$1");
            const _clean = _strip(iss.preview || iss.reason || "");
            const preview = _clean.length > 110 ? _clean.slice(0, 108) + "…" : _clean;
            return (
              <button key={iss.id} className={"card issue issue--" + sKey} onClick={() => onOpenIssue(iss)}>
                <span className={"issue__sev " + sMod}>
                  <IOSIcon name={iconName} size={16} />
                </span>
                <span className="issue__body">
                  <div className="issue__titlerow">
                    <p className="issue__title">{iss.title}</p>
                    {statusLabel && statusBadgeColor && (
                      <Badge size="xsmall" variant="weak" color={statusBadgeColor} className="issue__badge">
                        {statusLabel}
                      </Badge>
                    )}
                  </div>
                  {preview && <p className="issue__preview">{preview}</p>}
                  <span className="issue__meta">
                    <span className={"issue__sevlabel issue__sevlabel--" + sKey}>{sevLabel}</span>
                    <span className="issue__meta-dot" />
                    <span>{iss.location_in_contract || "위치 정보 없음"}</span>
                  </span>
                </span>
                <span className="issue__chev"><IOSIcon name="chev-right" size={18} /></span>
              </button>
            );
          })}
        </div>

        {/* Redflags checklist — 목업 시나리오에만 노출, 실제 분석 결과는 생략 */}
        {hasRedflags && (
          <div className="inset-group">
            <div className="inset-group__header">
              {data.contract_type === "employment" ? "근로계약 위험 신호 (추후 업데이트)" : "전세사기 체크리스트 16종"}
            </div>
            <div className="inset-group__list">
              {data.redflags.slice(0, 8).map((rf, i) => (
                <div className="inset-row" key={i} style={{ cursor: "default" }}>
                  <span className="inset-row__icon" style={{
                    background: rf.detected ? "var(--alert-soft)" : "var(--safe-soft)",
                    color: rf.detected ? "var(--alert)" : "var(--safe)",
                  }}>
                    <IOSIcon name={rf.detected ? "warn-tri" : "check"} size={16} />
                  </span>
                  <span className="inset-row__label" style={{ fontSize: 15 }}>{rf.label}</span>
                  <span className="inset-row__value" style={{
                    color: rf.detected ? "var(--alert)" : "var(--label-secondary)",
                    fontWeight: rf.detected ? 600 : 400,
                    opacity: rf.detected ? 1 : 0.55,
                  }}>
                    {rf.detected ? "감지됨" : "정상"}
                  </span>
                </div>
              ))}
            </div>
            <div className="inset-group__footer">
              16개 체크 포인트 중 {redflagCount}개가 걸렸어요. 나머지는 괜찮아요.
            </div>
          </div>
        )}

        {/* 실제 분석 결과일 때만 보이는 보조 섹션들 */}
        {analysis && data.next_steps && data.next_steps.length > 0 && (
          <div className="inset-group">
            <div className="inset-group__header">지금 바로 해 둘 일</div>
            <div className="inset-group__list">
              {data.next_steps.slice(0, 6).map((step, i) => (
                <div className="inset-row" key={i} style={{ cursor: "default", alignItems: "flex-start", padding: "12px 16px" }}>
                  <span className="inset-row__icon" style={{ background: "var(--brand)", color: "#fff", flexShrink: 0 }}>
                    <span style={{ fontSize: 12, fontWeight: 700 }}>{i + 1}</span>
                  </span>
                  <span style={{ flex: 1, fontSize: 14, lineHeight: 1.5, color: "var(--label)" }}>{step}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* numeric_audits 분기 ─
              주요 숫자 분석 섹션: status === ok 또는 mismatch 만 (실제 계약서에
                있는 숫자를 검증한 결과 — 통과 / 불일치).
              참고 섹션: status === warning (조항이 계약서에 없는 케이스 —
                연체이자율/위약금 등 법으로 자동 보호되는 항목).
            사용자 피드백: "warning 인 숫자 항목들은 결국 법으로 보호되니
            아래 회색 섹션에 두는 게 낫다". */}
        {(() => {
          const audits = Array.isArray(data.numeric_audits) ? data.numeric_audits : [];
          const _statusOf = (na) => (na?.status || "warning").toLowerCase();
          const inContract = audits.filter((na) => {
            const s = _statusOf(na);
            return s === "ok" || s === "mismatch";
          });
          const lawProtected = audits.filter((na) => _statusOf(na) === "warning");

          /* — 1) 주요 숫자 분석 (ok / mismatch) — */
          const renderInContract = analysis && inContract.length > 0 && (
            <div className="missing-block" style={{ marginTop: 12 }}>
              <div className="missing-block__head">
                <span
                  className="missing-block__head-icon"
                  style={{ background: "#fff3a3", color: "#8b6914" }}
                >
                  <IOSIcon name="info" size={14} weight={2.4} />
                </span>
                <span className="missing-block__title">
                  주요 숫자 분석 · {inContract.length}건
                </span>
              </div>
              <div className="missing-block__sub">
                {_renderHighlighted("보증금·계약금·잔금·연체이자·위약금 같은 [[K]]계약서 핵심 숫자[[/K]] 를 자동으로 따져봤어요. ✓ 는 통과, ⚠️ 는 확인 필요.")}
              </div>
              {inContract.map((na, i) => {
                const status = _statusOf(na);
                const isOk = status === "ok";
                const isMismatch = status === "mismatch";
                const iconName = isOk ? "check" : "exclam";
                /* TDS yellow-700 은 실제 orange 라 직접 색 지정. */
                const iconBg = isOk ? "var(--safe-soft)" : "var(--alert-soft)";
                const iconFg = isOk ? "var(--safe)" : "var(--alert)";
                const borderColor = isOk ? "var(--safe)" : "var(--alert)";
                return (
                  <div
                    className="missing-card"
                    key={`ic-${i}`}
                    style={{ borderLeftColor: borderColor }}
                  >
                    <span
                      className="missing-card__icon"
                      style={{ background: iconBg, color: iconFg }}
                    >
                      <IOSIcon name={iconName} size={15} weight={2.4} />
                    </span>
                    <span className="missing-card__title">{_renderHighlighted(na.check)}</span>
                    {na.detail && <span className="missing-card__reason">{_renderHighlighted(na.detail)}</span>}
                  </div>
                );
              })}
            </div>
          );

          /* — 2) 참고하면 좋은 항목 (missing_items + warning numeric_audits) — */
          const missingArr = (analysis && Array.isArray(data.missing_items)) ? data.missing_items : [];
          const totalReference = missingArr.length + lawProtected.length;
          const renderReference = totalReference > 0 && (
            <div className="missing-block">
              <div className="missing-block__head">
                <span
                  className="missing-block__head-icon"
                  style={{
                    background: "var(--fill-3, #F4F6F9)",
                    color: "var(--label-secondary, #4e5968)",
                  }}
                >
                  <IOSIcon name="info" size={14} weight={2.4} />
                </span>
                <span className="missing-block__title">
                  참고하면 좋은 항목 · {totalReference}건
                </span>
              </div>
              <div className="missing-block__sub">
                아래 항목들은 대부분 법으로 보호돼요. 그래도 불안하다면 계약서에 한 줄 적어두면 마음이 더 편해져요.
              </div>
              {/* numeric_audits warning 먼저 (연체이자율/위약금 등), 그 다음 missing_items */}
              {lawProtected.map((na, i) => (
                <div
                  className="missing-card"
                  key={`lp-${i}`}
                  style={{ borderLeftColor: "var(--separator, #E5E8EB)" }}
                >
                  <span
                    className="missing-card__icon"
                    style={{
                      background: "var(--fill-3, #F4F6F9)",
                      color: "var(--label-secondary, #4e5968)",
                    }}
                  >
                    <IOSIcon name="info" size={15} weight={2.2} />
                  </span>
                  <span className="missing-card__title">{_renderHighlighted(na.check)}</span>
                  {na.detail && <span className="missing-card__reason">{_renderHighlighted(na.detail)}</span>}
                </div>
              ))}
              {missingArr.map((m, i) => (
                <div
                  className="missing-card"
                  key={`mi-${i}`}
                  style={{ borderLeftColor: "var(--separator, #E5E8EB)" }}
                >
                  <span
                    className="missing-card__icon"
                    style={{
                      background: "var(--fill-3, #F4F6F9)",
                      color: "var(--label-secondary, #4e5968)",
                    }}
                  >
                    <IOSIcon name="info" size={15} weight={2.2} />
                  </span>
                  <span className="missing-card__title">{_renderHighlighted(m.title)}</span>
                  {m.reason && <span className="missing-card__reason">{_renderHighlighted(m.reason)}</span>}
                </div>
              ))}
            </div>
          );

          return (
            <>
              {renderInContract}
              {renderReference}
            </>
          );
        })()}

        {analysis && data.disclaimer && (
          <div style={{ marginTop: 20 }}>
            <BottomInfo>
              <p>{data.disclaimer}</p>
            </BottomInfo>
          </div>
        )}

        <div style={{ padding: "8px 20px 24px", display: "flex", flexDirection: "column", gap: 8 }}>
          <button className="btn btn--filled btn--large btn--block" onClick={onOpenPreview}>
            원문에서 해당 조항 보기
          </button>
          {/* 앱인토스에선 saveBase64Data 로 네이티브 파일 저장 대화상자, 웹에선
              <a download> 폴백 (TossAdapter.data.saveBase64). 분석 결과가 실제로
              있을 때만 활성화 — 시나리오 목업 화면에선 저장해 봐야 의미 없음. */}
          <button
            className="btn btn--gray btn--block"
            style={{ minHeight: 44, opacity: (!analysis || pdfBusy) ? 0.5 : 1 }}
            onClick={exportPdf}
            disabled={!analysis || pdfBusy}
            aria-busy={pdfBusy ? "true" : "false"}
          >
            {pdfBusy ? "PDF 만드는 중…" : "PDF 리포트로 저장하기"}
          </button>
        </div>
      </div>
    </div>
  );
}

window.ResultScreen = ResultScreen;

Object.assign(window, { ResultScreen });
