/* ============================================================================
 * 서명전에 — iOS Issue Detail (sheet content)
 * ========================================================================== */

// 두 가지 하이라이트 마커를 모두 <mark className="quote__hl"> 로 변환:
//   1) [[K]]...[[/K]]  — Result.jsx 의 _renderHighlighted 와 통일된 신규 마커
//   2) **...**          — 기존 markdown bold 마커 (호환 유지)
// LLM 이 둘 중 어느 쪽을 써도 안전하게 렌더된다.
function renderQuoteWithHighlights(text) {
  if (!text) return null;
  const parts = String(text).split(/(\[\[K\]\][^\[]*?\[\[\/K\]\]|\*\*[^*]+\*\*)/g);
  return parts.map((p, i) => {
    if (p.startsWith("[[K]]") && p.endsWith("[[/K]]")) {
      return <mark key={i} className="quote__hl">{p.slice(5, -6)}</mark>;
    }
    if (/^\*\*[^*]+\*\*$/.test(p)) {
      return <mark key={i} className="quote__hl">{p.slice(2, -2)}</mark>;
    }
    return <React.Fragment key={i}>{p}</React.Fragment>;
  });
}

/* reason(설명) 단락을 본문 + TL;DR(풀어쓴 한마디) 두 덩어리로 쪼갠다.
 * LLM 이 "쉽게 말해 …", "한마디로 …" 같은 요약 문장을 덧붙여 주는 편인데,
 * 기존에는 전부 한 회색 단락으로 묶여 있어서 "이 조항이 뭔 말이냐" 라는
 * 사용자 입장의 핵심 줄이 묻혀 있었다. 이 함수는:
 *   1) 문두에 가장 먼저 등장하는 요약 트리거 어구를 찾고
 *   2) 그 앞 → body, 뒤 → tldr 로 분리해 둘 다 반환
 * 트리거가 없으면 전체를 body 한 덩어리로.
 * 외부에선 `{ body, tldr }` 형태로 받아 시각 분리 렌더링.
 */
function splitReasonChunks(raw) {
  const text = String(raw || "").trim();
  if (!text) return { body: "", tldr: "" };
  // 앞쪽일수록 우선 (본문이 있고 그 뒤에 TL;DR 이 오는 구조가 대다수).
  const markers = ["쉽게 말해", "한마디로", "간단히 말해", "요약하면", "정리하면", "즉,"];
  let bestIdx = -1;
  for (const m of markers) {
    const idx = text.indexOf(m);
    if (idx > 0 && (bestIdx === -1 || idx < bestIdx)) bestIdx = idx;
  }
  if (bestIdx > 0) {
    const body = text.slice(0, bestIdx).replace(/[\s·,;.]+$/u, "").trim();
    const tldr = text.slice(bestIdx).trim();
    if (body && tldr) return { body, tldr };
  }
  return { body: text, tldr: "" };
}

/* ────────────────────────────────────────────────────────────────────
 * step 후처리 헬퍼 (의미적 dedup + off-topic 필터).
 *
 * 회귀 케이스:
 *   (A) 도어록 50만원 정액 배상 이슈 — LLM 이 4개 step 을 만드는데 3·4번이
 *       사실상 같은 메시지("50만원이 과도하다"). 사용자: "한 줄로 합쳐 줘".
 *   (B) 택배 분실·파손 이슈 — step 3 에 "특히 대금 지급일, 해지 절차,
 *       손해배상 범위를 계약서에 구체적인 숫자와 날짜로 적어 달라" 같은
 *       완전히 무관한 일반 권고가 끼어들어옴. 사용자: "택배랑 상관 없는
 *       이야기".
 *
 * 두 케이스를 모두 잡되 정상 step 은 보존:
 *   ① _stepTokens(s) — 한글 2자+ 명사 + 숫자(만/원/일 등) 추출, stop word 제거
 *   ② _jaccard(a, b) — 두 set 의 교집합/합집합. 1.0=동일, 0=무관
 *   ③ _isShortEncouragement(s) — 마무리 격려/안내 (35자 이하 + 권유 어미) 보호
 * ──────────────────────────────────────────────────────────────────── */
const _STEP_STOP_WORDS = new Set([
  "요청", "수정", "변경", "추가", "삭제", "공정", "기재", "포함", "확인", "계약서",
  "조항", "특약", "문구", "내용", "부분", "사항", "부탁", "말씀", "답변",
  "요구", "제안", "협의", "합의", "필요", "가능", "정상", "구체", "정확",
  "상황", "경우", "관련", "다음", "이렇게", "그렇게", "이거", "이건", "저거",
  "오늘", "지금", "원래", "보통", "대부분", "사람", "본인",
]);
function _stepTokens(s) {
  if (!s) return new Set();
  const out = new Set();
  const numRe = /(\d[\d,]*\s*(?:만\s*원|원|일|개월|년|배|%|회))/g;
  let m;
  while ((m = numRe.exec(s)) !== null) {
    out.add(m[0].replace(/\s+/g, ""));
  }
  const wordRe = /[가-힣]{2,}/g;
  while ((m = wordRe.exec(s)) !== null) {
    const w = m[0];
    if (_STEP_STOP_WORDS.has(w)) continue;
    out.add(w);
  }
  return out;
}
/* 한국어 조사·어미 탓에 같은 어휘가 다른 토큰으로 보이는 회귀 방어:
   "관리사무소가" / "관리사무소와" / "관리사무소" 가 본래 같은 단어인데
   `[가-힣]{2,}` 단순 매칭은 모두 별개 토큰으로 만든다. 두 토큰의 공통
   prefix 가 3자 이상이고 길이 차이가 2 이하면 "같은 어휘" 로 간주. */
function _tokenSimilar(a, b) {
  if (a === b) return true;
  if (a.startsWith(b) || b.startsWith(a)) {
    return Math.min(a.length, b.length) >= 3;
  }
  let i = 0;
  const lim = Math.min(a.length, b.length);
  while (i < lim && a[i] === b[i]) i += 1;
  return i >= 3 && Math.abs(a.length - b.length) <= 2;
}
function _hasSimilarToken(set, target) {
  for (const x of set) {
    if (_tokenSimilar(x, target)) return true;
  }
  return false;
}
function _jaccard(a, b) {
  if (a.size === 0 && b.size === 0) return 0;
  // prefix-기반 매칭: a 의 각 토큰이 b 안의 비슷한 토큰과 매칭되는지
  let inter = 0;
  const matched = new Set();
  for (const x of a) {
    for (const y of b) {
      if (matched.has(y)) continue;
      if (_tokenSimilar(x, y)) {
        inter += 1;
        matched.add(y);
        break;
      }
    }
  }
  const uni = a.size + b.size - inter;
  return uni === 0 ? 0 : inter / uni;
}
function _isShortEncouragement(s) {
  const t = String(s || "").trim();
  if (t.length > 35) return false;
  return /(?:할게요|줄\s*거예요|좋아요|보세요|해\s*줘요|봐\s*줘요|봐요)\.?$/.test(t);
}

function IssueDetail({ issue, onGoPreview }) {
  if (!issue) return null;

  /* 칩 색은 legal_status 우선으로 결정 — Result hero 의 heroKind 와 동일
     매핑 (void→alert 빨강 / unfair→warn 주황 / negotiable→safe 초록).
     이렇게 해야 "이슈 리스트에서 보던 색" 과 "이슈 상세 칩 색" 이 일치해서
     사용자 혼란이 없음. legal_status 가 없으면 severity 로 폴백.
     라벨 텍스트는 기존처럼 "고위험 · 한쪽에 불리" 조합 그대로 유지 — 정보량
     손실 없음. */
  const toneByLegal =
    issue.legal_status === "void"       ? { bg: "var(--alert-soft)", color: "var(--alert)" } :
    issue.legal_status === "unfair"     ? { bg: "var(--warn-soft)",  color: "var(--warn)"  } :
    issue.legal_status === "negotiable" ? { bg: "var(--safe-soft)",  color: "var(--safe)"  } :
    null;
  const toneBySev =
    issue.severity === "high"   ? { bg: "var(--alert-soft)", color: "var(--alert)" } :
    issue.severity === "medium" ? { bg: "var(--warn-soft)",  color: "var(--warn)"  } :
    { bg: "var(--safe-soft)", color: "var(--safe)" };
  const sevLabel =
    issue.severity === "high"   ? "고위험" :
    issue.severity === "medium" ? "주의"   :
    "경미";
  const sev = { ...(toneByLegal || toneBySev), label: sevLabel };

  const statusLabel =
    issue.legal_status === "void" ? "효력 없을 수도" :
    issue.legal_status === "unfair" ? "한쪽에 불리" :
    issue.legal_status === "negotiable" ? "조율할 부분" : "";

  /* step 분할 — 마침표(. 。) + 공백 으로 split 후, 따옴표 짝이 안 맞는
     인접 step 들을 다시 merge. 그렇지 않으면 LLM 이 만든 인용문
     ("'갑자기 오시면 곤란해요. 하루 전에 말씀해 주세요?'라고 해보세요")
     이 마침표 위치에서 잘려 step 4·5 가 따옴표 한 짝씩 외롭게 나뉨.
     같은 의미라면 짧은 step 도 인접 시 자동으로 합쳐서 step 수를 줄임. */
  const steps = (() => {
    const raw = (issue.suggestion || "")
      .split(/(?<=[.。?!])\s+/)
      .map((s) => s.trim())
      .filter(Boolean);
    /* 모든 종류의 따옴표 (영문 / 한글 / curly) 갯수 파악. 짝수 = 닫힘. */
    const _quoteCount = (s) => {
      let n = 0;
      for (const ch of s) {
        if (ch === '"' || ch === "'" || ch === "\u201c" || ch === "\u201d"
            || ch === "\u2018" || ch === "\u2019" || ch === "\u300c" || ch === "\u300d"
            || ch === "\u300e" || ch === "\u300f") {
          n += 1;
        }
      }
      return n;
    };
    /* 1단계: 따옴표 짝이 안 맞으면 다음과 merge */
    const merged = [];
    let buf = "";
    for (const part of raw) {
      const cur = buf ? (buf + " " + part) : part;
      if (_quoteCount(cur) % 2 === 1) {
        // 아직 닫히지 않은 따옴표 — 다음 step 과 합치기 위해 buffer 에 보관
        buf = cur;
      } else {
        merged.push(cur);
        buf = "";
      }
    }
    if (buf) merged.push(buf); // 끝까지 안 닫힌 따옴표가 있으면 그대로 flush
    /* 2단계: 너무 짧은 인접 step (30자 미만) 들이 의미 단절 없이 이어진다면
       추가 merge — "원하면 한 줄 넣어두면 좋아요." (짧은 후속 권유) 같은
       단편을 앞 step 에 흡수. 단 문장이 명확히 다른 액션이면 보존. */
    const sentencePass = [];
    for (const s of merged) {
      const last = sentencePass[sentencePass.length - 1];
      if (last && last.length < 30 && s.length < 30
          && /(?:해요|좋아요|보세요|어때요|할게요)\.?$/.test(last)) {
        sentencePass[sentencePass.length - 1] = last + " " + s;
      } else {
        sentencePass.push(s);
      }
    }

    /* 3단계: 의미적 dedup — 두 step 이 같은 얘기를 두 번 하면 짧은 쪽을 drop.
       기준 두 가지를 OR 로 적용:
         (a) 토큰 jaccard ≥ 0.5 — 표현은 달라도 단어 set 이 절반 이상 겹침
         (b) 같은 숫자 토큰("50만원" 등) 공유 + jaccard ≥ 0.15 — 정확한 금액을
             양쪽이 동시에 언급하면 같은 주제일 가능성이 매우 높음
       회귀: 도어록 50만원 이슈에서 "50만원은 너무 과도하다" + "50만원은
       시세보다 2~3배 비싸다" — 다른 형용사를 써서 jaccard 0.16 인데 사용자는
       "한 줄로 합쳐 줘" 요청. (b) 룰이 이 케이스를 잡는다. */
    const tokenized = sentencePass.map((s) => ({
      text: s,
      toks: _stepTokens(s),
      nums: [..._stepTokens(s)].filter((t) => /^\d/.test(t)),
    }));
    const drop = new Set();
    for (let i = 0; i < tokenized.length; i++) {
      if (drop.has(i)) continue;
      for (let j = i + 1; j < tokenized.length; j++) {
        if (drop.has(j)) continue;
        const sim = _jaccard(tokenized[i].toks, tokenized[j].toks);
        const sharedNum =
          tokenized[i].nums.length > 0 &&
          tokenized[j].nums.length > 0 &&
          tokenized[i].nums.some((n) => tokenized[j].nums.includes(n));
        const isDup = sim >= 0.5 || (sharedNum && sim >= 0.15);
        if (isDup) {
          // 짧은 쪽 drop (긴 쪽에 정보가 더 있음)
          const dropIdx = tokenized[i].text.length >= tokenized[j].text.length ? j : i;
          drop.add(dropIdx);
          if (dropIdx === i) break;
        }
      }
    }
    const deduped = tokenized.filter((_, i) => !drop.has(i));

    /* 4단계: off-topic 필터 — issue 주제와 한 단어도 안 겹치는 긴 step 은 drop.
       회귀: 택배 분실·파손 이슈에 "특히 대금 지급일, 해지 절차, 손해배상 범위를
       계약서에 구체적인 숫자와 날짜로 적어 달라" 같은 일반 권고가 끼어드는 케이스.
       단, 짧은 마무리 격려체 ("대부분 수정해 줄 거예요" 등) 는 보호. */
    const topicSrc = [issue.title || "", issue.suggested_replacement || ""].join(" ");
    const topicToks = _stepTokens(topicSrc);
    const onTopic = deduped.filter(({ text, toks }) => {
      if (topicToks.size === 0) return true; // 비교 기준 없으면 통과
      if (_isShortEncouragement(text)) return true;
      // step 의 의미 토큰이 issue 주제와 한 단어라도 겹치면 keep
      // (조사 차이 대비 prefix matching — _hasSimilarToken)
      for (const t of toks) {
        if (_hasSimilarToken(topicToks, t)) return true;
      }
      // 토큰 한 개도 안 겹치고 길이도 충분히 길면 무관 step → drop
      return text.length < 25; // 짧으면 일단 keep (일반 마무리 가능성)
    });

    return onTopic.map((x) => x.text);
  })();

  return (
    <div className="issue-detail">
      <span
        className="issue-detail__sev"
        style={{ background: sev.bg, color: sev.color }}
      >
        <IOSIcon name="exclam" size={13} />
        {sev.label} · {statusLabel}
      </span>
      <h2 className="issue-detail__title">{issue.title}</h2>

      {issue.quote && (
        <div className="quote">
          <div className="quote__label">계약서에 적힌 문구 ({issue.location_in_contract || "위치 정보 없음"})</div>
          <div className="quote__body">
            <span className="quote__mark">“</span>
            <span className="quote__ellipsis">…</span>
            {renderQuoteWithHighlights(issue.quote)}
            <span className="quote__ellipsis">…</span>
            <span className="quote__mark">”</span>
          </div>
        </div>
      )}

      {(() => {
        const { body, tldr } = splitReasonChunks(issue.reason);
        if (!body && !tldr) return null;
        return (
          <div className="issue-detail__reason">
            {body && (
              <p className="reason-body">{renderQuoteWithHighlights(body)}</p>
            )}
            {tldr && (
              <div className="reason-tldr" role="note" aria-label="한 줄로">
                <span className="reason-tldr__icon" aria-hidden="true">
                  <IOSIcon name="info" size={16} />
                </span>
                <div className="reason-tldr__content">
                  <span className="reason-tldr__label">한 줄로</span>
                  <p className="reason-tldr__text">{renderQuoteWithHighlights(tldr)}</p>
                </div>
              </div>
            )}
          </div>
        );
      })()}

      {steps.length > 0 && (
        <div className="steps">
          <h4 className="steps__title">이렇게 고쳐 달라고 말해 봐요</h4>
          {steps.map((s, i) => (
            <div key={i} className="step">
              <span className="step__num tnum">{i + 1}</span>
              <p className="step__text">{renderQuoteWithHighlights(s)}</p>
            </div>
          ))}
        </div>
      )}

      {/* 바로 넣어볼 문구 (suggested_replacement 전용) — 해요체 안내 "말해 봐요"
          리스트와 섞이면 톤이 튀어서 의도적으로 분리. 평서 어미 모범조항은 여기
          에서 단독 카드로. 복사해서 계약서/특약에 넣을 수 있게 시각적으로 구분
          (견적서 같은 quote 박스 톤). */}
      {issue.suggested_replacement && (
        <div style={{
          padding: "14px 16px",
          background: "var(--fill-3, #F4F6F9)",
          borderRadius: 12,
          marginBottom: 16,
          border: "1px solid var(--line-1, #EEE)",
        }}>
          <div style={{
            fontSize: 12,
            fontWeight: 600,
            color: "var(--label-secondary, #4e5968)",
            marginBottom: 6,
            letterSpacing: "-0.01em",
          }}>
            바로 넣어볼 문구
          </div>
          <p style={{
            margin: 0,
            fontSize: 14,
            lineHeight: 1.55,
            color: "var(--ink, #191F28)",
            fontFamily: "var(--font-text)",
          }}>
            {renderQuoteWithHighlights(issue.suggested_replacement)}
          </p>
        </div>
      )}

      {issue.law_reference && (
        <div style={{
          padding: "12px 14px",
          background: "var(--brand-soft)",
          borderRadius: 12,
          fontSize: 14,
          color: "var(--brand-ink, var(--brand))",
          marginBottom: 18,
          display: "flex", gap: 10, alignItems: "flex-start",
        }}>
          <IOSIcon name="info" size={17} />
          <div>
            <strong style={{ fontWeight: 600 }}>근거 법령</strong>
            <div style={{ marginTop: 2, opacity: 0.85, fontSize: 13 }}>{issue.law_reference}</div>
          </div>
        </div>
      )}

      <button className="btn btn--filled btn--large btn--block" onClick={onGoPreview}>
        원문에서 이 문구 찾아보기
      </button>
    </div>
  );
}

window.IssueDetail = IssueDetail;

Object.assign(window, { IssueDetail });
