/* =============================================================================
   정책·규정 지식베이스 (문서 §5)
   -----------------------------------------------------------------------------
   §5.1 지식베이스 대상:
     N2SF 보안 가이드라인·모델2 문서 / 산업은행 내부 정보보호 규정 / 망분리·
     생성형 AI 활용 관련 내부 정책 / C·S·O 분류 기준 / N2SF 통제 항목 매핑 /
     MITRE ATT&CK 전술·기술 정의서
   §5.2 벡터DB + 그래프DB 병행:
     벡터DB — 문단·조항 단위 의미 임베딩 → 자연어 유사 검색
     그래프DB — 조항·정보자산·데이터 등급·사용자 역할·통제영역·이벤트 유형의
               관계를 노드/엣지로 구조화
   §5.3 RAG + Reranker 흐름:
     통합 이벤트 요약 질의 → 벡터DB 1차 검색 → Reranker로 관련성 재배치 →
     그래프DB 관계 정보 조회 → LLM 프롬프트 주입 → 자연어 설명 생성
============================================================================= */

const KnowledgeBase = () => {
  const M = window.MOCK;
  const [q, setQ] = useState("S등급 데이터가 포함된 프롬프트의 외부 전송 시도");
  const [sel, setSel] = useState(M.policies[0].id);
  const [searching, setSearching] = useState(false);

  const selected = M.policies.find(p => p.id === sel);

  const runSearch = () => {
    setSearching(true);
    setTimeout(() => setSearching(false), 800);
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>정책 · 규정 지식베이스</h1>
          <div className="sub">벡터DB · 그래프DB 병행 구조 + RAG · Reranker 기반 의미 검색</div>
        </div>
      </div>

      <div className="card dash-gap" style={{background:"var(--bg-2)"}}>
        <div className="row gap-8" style={{marginBottom:10, maxWidth:640}}>
          <div className="search-wrap" style={{flex:1}}>
            <Icon name="search" size={14}/>
            <input className="search" value={q} onChange={e=>setQ(e.target.value)} placeholder="자연어로 상황을 질의하세요"/>
          </div>
          <button className="btn primary" onClick={runSearch} style={{flex:"none"}}>
            <Icon name="search" size={13}/> 의미 검색
          </button>
        </div>
        {searching && (
          <div className="row gap-8" style={{marginTop:10, padding:"8px 10px", background:"var(--surface-2)", borderRadius:6}}>
            <span className="sev-dot info"/>
            <span className="small text-2">벡터DB 조회 중 · 유사 조항 12건 후보 → Reranker 적용 → 상위 6건 재정렬…</span>
          </div>
        )}
      </div>

      <div className="grid dash-gap" style={{gridTemplateColumns:"minmax(0, 1fr) minmax(0, 1.3fr)", alignItems:"stretch"}}>
        <Section title={`검색 결과 · ${M.policies.length}건`} meta="의미 유사도 순"
          style={{display:"flex", flexDirection:"column", minHeight:0, maxHeight:720}}
          bodyStyle={{overflowY:"auto", flex:1, minHeight:0, paddingRight:4}}
        >
          {M.policies.map((p,i) => (
            <div key={p.id} onClick={()=>setSel(p.id)}
              style={{padding:"12px 10px", borderRadius:6, cursor:"pointer", marginBottom:6,
                background: sel===p.id ? "color-mix(in oklch, var(--accent) 10%, transparent)" : "var(--surface-2)",
                border: "1px solid " + (sel===p.id ? "color-mix(in oklch, var(--accent) 40%, transparent)" : "var(--border)")}}>
              <div className="row between mb-8">
                <span className="mono small" style={{color:"var(--accent)"}}>{p.id}</span>
                <span className="mono small muted">유사도 {(0.95 - i*0.07).toFixed(2)}</span>
              </div>
              <div style={{fontSize:13.5, fontWeight:500, marginBottom:4}}>{p.title}</div>
              <div className="small muted" style={{marginBottom:6}}>{p.org}</div>
              <div className="small text-2" style={{
                overflow:"hidden", display:"-webkit-box", WebkitLineClamp:2, WebkitBoxOrient:"vertical"
              }}>{p.excerpt}</div>
              <div className="row gap-4" style={{marginTop:8, flexWrap:"wrap"}}>
                {p.tags.map(t => <span key={t} className="chip" style={{fontSize:11, padding:"2px 7px"}}>{t}</span>)}
              </div>
            </div>
          ))}
        </Section>

        <div>
          <Section title={selected.id + " · " + selected.title} meta={selected.org} style={{marginBottom:12}}
            right={<span className="badge info">정규화 조항</span>}
          >
            <div style={{padding:"12px 14px", background:"var(--surface-2)", borderLeft:"2px solid var(--accent)", borderRadius:"0 6px 6px 0", fontSize:14, lineHeight:1.7, color:"var(--text)"}}>
              {selected.excerpt}
            </div>
            <div className="row gap-4" style={{marginTop:12, flexWrap:"wrap"}}>
              {selected.tags.map(t => <span key={t} className="chip">{t}</span>)}
            </div>
          </Section>

          <Section title="그래프 연계" meta="조항 · 등급 · 통제영역 · 이벤트">
            <GraphView policyId={selected.id}/>
          </Section>
        </div>
      </div>

      <div className="grid grid-3">
        <div className="card">
          <div className="small muted mb-8">등록된 정책 문서</div>
          <div className="mono" style={{fontSize:24, fontWeight:600}}>184</div>
          <div className="small muted">N2SF 가이드 · 내부 규정 · 국가 보안정책</div>
        </div>
        <div className="card">
          <div className="small muted mb-8">벡터 임베딩 청크</div>
          <div className="mono" style={{fontSize:24, fontWeight:600}}>12,408</div>
          <div className="small muted">문단 · 조항 단위 분할 저장</div>
        </div>
        <div className="card">
          <div className="small muted mb-8">그래프 노드 · 엣지</div>
          <div className="mono" style={{fontSize:24, fontWeight:600}}>3,214 <span className="muted" style={{fontWeight:400, fontSize:15}}>· 8,902</span></div>
          <div className="small muted">조항 · 자산 · 등급 · 역할 관계</div>
        </div>
      </div>
    </div>
  );
};

// 간이 그래프 뷰 (SVG) — Graph RAG 스타일
const GraphView = ({ policyId }) => {
  // 중심: 정책 노드. 주변에 조항·등급·통제영역·역할·이벤트·자산 노드와 관계명 엣지
  const nodes = [
    { id: policyId,        x: 260, y: 160, label: policyId,          type: "policy", r: 22 },
    { id: "N2SF-M2-D-06",  x:  90, y:  90, label: "N2SF-M2-D-06",    type: "policy", r: 14 },
    { id: "INT-GAI-05",    x:  90, y: 220, label: "INT-GAI-05",      type: "policy", r: 14 },
    { id: "S등급",          x: 260, y:  40, label: "S등급 데이터",      type: "grade",  r: 16 },
    { id: "C등급",          x: 400, y:  70, label: "C등급 데이터",      type: "grade",  r: 12 },
    { id: "외부전송",        x: 460, y: 160, label: "외부 전송 영역",    type: "area",   r: 16 },
    { id: "내부망",          x: 420, y: 260, label: "내부망",           type: "area",   r: 12 },
    { id: "심사역",          x: 140, y: 300, label: "심사역 역할",       type: "role",   r: 14 },
    { id: "고객DB",          x: 280, y: 290, label: "T_CUSTOMER",      type: "asset",  r: 13 },
    { id: "AIGW차단",        x: 380, y: 220, label: "AIGW 차단 이벤트", type: "event",  r: 15 },
  ];
  const edges = [
    { f: policyId,       t: "S등급",       rel: "적용 대상" },
    { f: policyId,       t: "외부전송",     rel: "통제 영역" },
    { f: policyId,       t: "심사역",       rel: "책임 주체" },
    { f: policyId,       t: "AIGW차단",    rel: "실행 이벤트" },
    { f: policyId,       t: "N2SF-M2-D-06", rel: "상위 조항" },
    { f: policyId,       t: "INT-GAI-05",  rel: "보완 규정" },
    { f: "S등급",         t: "고객DB",      rel: "포함" },
    { f: "AIGW차단",      t: "외부전송",     rel: "차단" },
    { f: "고객DB",        t: "심사역",       rel: "접근 허용" },
    { f: "외부전송",       t: "C등급",        rel: "허용 조건" },
    { f: "AIGW차단",      t: "내부망",       rel: "발생 위치" },
  ];
  const typeColor = {
    policy: "var(--accent)",
    grade:  "var(--grade-s)",
    area:   "var(--high)",
    role:   "var(--info)",
    event:  "var(--critical)",
    asset:  "var(--low)",
  };
  const typeLabel = {
    policy: "조항", grade: "등급", area: "통제영역",
    role: "역할", event: "이벤트", asset: "자산",
  };
  const byId = Object.fromEntries(nodes.map(n => [n.id, n]));
  const [hover, setHover] = useState(null);

  // edge endpoint 계산 (원의 경계까지 당기기)
  const edgeLine = (a, b) => {
    const dx = b.x - a.x, dy = b.y - a.y;
    const d = Math.sqrt(dx*dx + dy*dy);
    const ux = dx/d, uy = dy/d;
    return { x1: a.x + ux*a.r, y1: a.y + uy*a.r, x2: b.x - ux*b.r, y2: b.y - uy*b.r };
  };

  const isActiveEdge = e => hover && (e.f === hover || e.t === hover);
  const isActiveNode = id => {
    if (!hover) return false;
    if (hover === id) return true;
    return edges.some(e => (e.f === hover && e.t === id) || (e.t === hover && e.f === id));
  };

  return (
    <div style={{position:"relative"}}>
      <svg viewBox="0 0 540 340" width="100%" style={{display:"block"}}>
        <defs>
          <marker id="gr-arrow" viewBox="0 0 10 10" refX="9" refY="5"
                  markerWidth="6" markerHeight="6" orient="auto-start-reverse">
            <path d="M0,0 L10,5 L0,10 z" fill="var(--border-2)"/>
          </marker>
          <marker id="gr-arrow-active" viewBox="0 0 10 10" refX="9" refY="5"
                  markerWidth="6" markerHeight="6" orient="auto-start-reverse">
            <path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/>
          </marker>
          <pattern id="gr-grid" width="20" height="20" patternUnits="userSpaceOnUse">
            <circle cx="1" cy="1" r="0.6" fill="var(--border)" opacity="0.4"/>
          </pattern>
        </defs>
        <rect width="540" height="340" fill="url(#gr-grid)"/>

        {/* edges */}
        {edges.map((e, i) => {
          const a = byId[e.f], b = byId[e.t];
          const ln = edgeLine(a, b);
          const active = isActiveEdge(e);
          const mx = (ln.x1 + ln.x2) / 2;
          const my = (ln.y1 + ln.y2) / 2;
          return (
            <g key={i} opacity={hover && !active ? 0.25 : 1} style={{transition:"opacity 0.15s"}}>
              <line x1={ln.x1} y1={ln.y1} x2={ln.x2} y2={ln.y2}
                stroke={active ? "var(--accent)" : "var(--border-2)"}
                strokeWidth={active ? 1.4 : 1}
                markerEnd={active ? "url(#gr-arrow-active)" : "url(#gr-arrow)"}/>
              {active && (
                <g>
                  <rect x={mx-28} y={my-8} width="56" height="15" rx="3"
                    fill="var(--surface-2)" stroke="var(--border)" strokeWidth="0.5"/>
                  <text x={mx} y={my+2} fontSize="9" fill="var(--text-2)" textAnchor="middle"
                    fontFamily="var(--font-sans)">{e.rel}</text>
                </g>
              )}
            </g>
          );
        })}

        {/* nodes */}
        {nodes.map(n => {
          const active = isActiveNode(n.id);
          const dim = hover && !active;
          return (
            <g key={n.id} opacity={dim ? 0.3 : 1}
               style={{cursor:"pointer", transition:"opacity 0.15s"}}
               onMouseEnter={()=>setHover(n.id)} onMouseLeave={()=>setHover(null)}>
              <circle cx={n.x} cy={n.y} r={n.r+4}
                fill={typeColor[n.type]} opacity={active ? 0.25 : 0.08}/>
              <circle cx={n.x} cy={n.y} r={n.r}
                fill={typeColor[n.type]} fillOpacity="0.22"
                stroke={typeColor[n.type]} strokeWidth={active ? 2 : 1.4}/>
              {n.type === "policy" && (
                <text x={n.x} y={n.y+3} fontSize="9" fill={typeColor[n.type]}
                  textAnchor="middle" fontFamily="var(--font-mono)" fontWeight="600">
                  {n.id === policyId ? "★" : "§"}
                </text>
              )}
              <text x={n.x} y={n.y + n.r + 13} fontSize="10.5"
                fill={active ? "var(--text)" : "var(--text-2)"}
                textAnchor="middle" fontFamily="var(--font-sans)"
                fontWeight={n.id === policyId ? 600 : 400}>
                {n.label}
              </text>
            </g>
          );
        })}
      </svg>

      {/* 범례 */}
      <div className="row gap-8" style={{
        flexWrap:"wrap", padding:"8px 10px", marginTop:4,
        background:"var(--surface-2)", borderRadius:6, border:"1px solid var(--border)"
      }}>
        {Object.entries(typeLabel).map(([k, label]) => (
          <span key={k} className="row gap-4" style={{fontSize:11.5, color:"var(--text-2)"}}>
            <span style={{
              width:8, height:8, borderRadius:"50%", background: typeColor[k], opacity: 0.8
            }}/>
            {label}
          </span>
        ))}
        <span style={{marginLeft:"auto", fontSize:11.5, color:"var(--text-3)"}}>
          노드 hover → 관계 하이라이트
        </span>
      </div>
    </div>
  );
};

window.KnowledgeBase = KnowledgeBase;
