/* --- src/wires.jsx --- */
// Adds global interactivity: search palette, notifications panel,
// and modal-based detail actions.
const { useState: useStateW, useEffect: useEffectW, useRef: useRefW } = React;

// ─────────── Toast bus disabled ───────────
const _toastListeners = [];
window.toast = function(msg, opts){
  // 시연 중 사례 카드나 버튼을 누를 때 뜨는 중앙 알림은 화면 집중을 방해하므로 표시하지 않습니다.
};

function ToastHost(){
  return null;
}

// ─────────── Search Palette ───────────
function SearchPalette({ open, onClose, go }){
  const [q, setQ] = useStateW('');
  const [reformPool, setReformPool] = useStateW([]);
  const inputRef = useRefW(null);
  useEffectW(()=>{
    if(open && inputRef.current) setTimeout(()=>inputRef.current.focus(), 50);
    const onKey = e => { if(e.key === 'Escape') onClose(); };
    if(open) document.addEventListener('keydown', onKey);
    return ()=> document.removeEventListener('keydown', onKey);
  }, [open]);
  useEffectW(()=>{
    if (!open) return;
    let alive = true;
    if (window.RSData && typeof window.RSData.loadDay5Metrics === 'function') {
      window.RSData.loadDay5Metrics()
        .then((m)=>{ if (alive && m?.reformPool) setReformPool(m.reformPool); })
        .catch(()=>{});
    }
    return ()=>{ alive = false; };
  }, [open]);
  if(!open) return null;
  const staticHits = [
    { type:'화면', label:'정비 검토 후보 (02)',               target:'dashboard' },
    { type:'화면', label:'신구조문 비교 (04)',                target:'compare' },
    { type:'화면', label:'사례 상세 분석 (08)',               target:'analysis' },
    { type:'화면', label:'정합성 리포트 (09)',                target:'insight' },
    { type:'화면', label:'기관별 정비 현황 (10)',             target:'agencies' },
    { type:'법령', label:'건축법 제4조의2 (심의 처리기한)',    target:'compare' },
    { type:'법령', label:'건축법 시행령 제5조 (중앙건축위원회)', target:'compare' },
  ];
  const reformHits = reformPool.map((r) => {
    const isA = r.grade === 'A' || r.source === 'direct-comparison' || r.source === 'policy-hunter';
    const articlePart = r.localArticle ? ` ${r.localArticle}` : '';
    const valuePart = (r.upperValue && r.lowerValue)
      ? ` — 상위법 ${r.upperValue} vs 조례 ${r.lowerValue}`
      : (r.worstErrorType ? ` — ${r.worstErrorType}` : '');
    return {
      type: isA ? '정비 후보 A' : '검토 신호 B',
      label: `${r.title}${articlePart}${valuePart}`,
      target: 'analysis',
      ordinance: r,
      grade: r.grade,
      source: r.source
    };
  });
  const allHits = [...reformHits, ...staticHits];
  const hits = allHits.filter(h => {
    if (!q.trim()) return true;
    const ql = q.toLowerCase();
    return h.label.toLowerCase().includes(ql) || h.type.toLowerCase().includes(ql);
  });
  return (
    <div onClick={onClose} style={{
      position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:9998,
      display:'flex', alignItems:'flex-start', justifyContent:'center',
      paddingTop:96, backdropFilter:'blur(4px)'
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width:'min(680px, 92vw)', background:'#fff', borderRadius:12,
        boxShadow:'0 24px 60px rgba(15,23,42,0.28)', overflow:'hidden'
      }}>
        <div style={{display:'flex', alignItems:'center', gap:10, padding:'14px 18px', borderBottom:'1px solid var(--line)'}}>
          <Icon name="search" size={16} color="var(--ink-3)"/>
          <input ref={inputRef} value={q} onChange={e=>setQ(e.target.value)}
            placeholder="법령 · 조문 · 서식 · 기관 · 화면 검색"
            style={{flex:1, border:'none', outline:'none', fontSize:14, fontFamily:'inherit'}}/>
          <span className="mono" style={{fontSize:10, color:'var(--ink-4)', padding:'2px 6px', background:'#F1F5F9', borderRadius:4}}>ESC</span>
        </div>
        <div style={{maxHeight:420, overflowY:'auto'}}>
          {hits.length === 0 ? (
            <div style={{padding:'40px 20px', textAlign:'center', fontSize:13, color:'var(--ink-3)'}}>검색 결과가 없습니다</div>
          ) : hits.map((h,i)=>{
            const isA = h.grade === 'A' || h.source === 'direct-comparison' || h.source === 'policy-hunter';
            const isB = h.grade === 'B' || h.source === 'indicator-only';
            const tagBg = isA ? '#FEE2E2' : isB ? '#FEF3C7' : 'var(--blue-soft)';
            const tagFg = isA ? '#991B1B' : isB ? '#854D0E' : 'var(--blue)';
            const onSelect = () => {
              if (h.ordinance) {
                try {
                  sessionStorage.setItem('syncLaw.analysisOrdinance', JSON.stringify({
                    ordinanceId: h.ordinance.ordinanceId,
                    mst: h.ordinance.mst,
                    title: h.ordinance.title,
                    agency: h.ordinance.agency,
                    source: h.ordinance.source || null,
                    grade: h.ordinance.grade || null
                  }));
                } catch {}
              }
              go(h.target);
              onClose();
              window.toast('이동: ' + h.label, {tone:'info'});
            };
            return (
              <button key={i} onClick={onSelect} style={{
                display:'flex', alignItems:'center', gap:12, width:'100%', padding:'10px 18px',
                border:'none', borderBottom: i<hits.length-1?'1px solid var(--line)':'none',
                background:'transparent', cursor:'pointer', textAlign:'left'
              }}
              onMouseEnter={e=> e.currentTarget.style.background='var(--blue-soft)'}
              onMouseLeave={e=> e.currentTarget.style.background='transparent'}>
                <span style={{fontSize:10, fontWeight:700, color:tagFg, background:tagBg, padding:'2px 8px', borderRadius:99, minWidth:62, textAlign:'center'}}>{h.type}</span>
                <span style={{flex:1, fontSize:13, color:'var(--ink)'}}>{h.label}</span>
                <Icon name="arrow-right" size={12} color="var(--ink-4)"/>
              </button>
            );
          })}
        </div>
        <div style={{padding:'10px 18px', borderTop:'1px solid var(--line)', background:'#F8FAFC', fontSize:11, color:'var(--ink-3)'}}>
          <span className="mono">↵</span> 이동 · <span className="mono">ESC</span> 닫기 · 의미 검색 · Claude·GPT-4o + KoBERT 법률 임베딩
        </div>
      </div>
    </div>
  );
}

// ─────────── Notifications Panel ───────────
function NotificationsPanel({ open, onClose, go }){
  if(!open) return null;
  const items = [
    { tone:'amber', date:'방금', title:'개인정보 보호법 제15조 개정 감지', body:'관련 하위규정 234건 추가 검토 필요', target:'upper' },
    { tone:'blue',  date:'2시간 전', title:'국민 제보 14건 신규 접수',       body:'AI 검증 후 4건이 탐지와 일치',  target:'report' },
    { tone:'teal',  date:'어제',     title:'광역시 X 옥외광고물 조례 정비 반영', body:'정합성 리포트 업데이트',     target:'insight' },
    { tone:'steel', date:'2일 전',   title:'주간 정비 현황 리포트 발행',       body:'전체 싱크Law율 78% (+2.4%p)',    target:'insight' },
    { tone:'amber', date:'3일 전',   title:'B부처 고시 미반영 검토 요청',     body:'대기환경보전법 개정 후 7건 미정비', target:'lower' },
  ];
  return (
    <div onClick={onClose} style={{
      position:'fixed', inset:0, zIndex:9997, background:'transparent'
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        position:'absolute', top:64, right:24, width:380, maxHeight:'70vh',
        background:'#fff', borderRadius:12, overflow:'hidden',
        boxShadow:'0 20px 50px rgba(15,23,42,0.22)', border:'1px solid var(--line)',
        display:'flex', flexDirection:'column'
      }}>
        <div style={{padding:'14px 18px', borderBottom:'1px solid var(--line)', display:'flex', justifyContent:'space-between', alignItems:'center'}}>
          <div>
            <div style={{fontSize:14, fontWeight:700, color:'var(--ink)'}}>알림</div>
            <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>새 알림 5건</div>
          </div>
          <button onClick={()=>{ window.toast('모두 읽음 처리되었습니다', {tone:'success'}); onClose(); }} style={{fontSize:11, color:'var(--blue)', background:'transparent', border:'none', fontWeight:600, cursor:'pointer'}}>모두 읽음</button>
        </div>
        <div style={{flex:1, overflowY:'auto'}}>
          {items.map((it,i)=>{
            const dotColor = it.tone==='amber'?'#C77A1B':it.tone==='blue'?'#2E5FB0':it.tone==='teal'?'#1F7A6B':'#5B7C99';
            return (
              <button key={i} onClick={()=>{ go(it.target); onClose(); }} style={{
                display:'block', width:'100%', textAlign:'left', padding:'12px 18px',
                border:'none', borderBottom: i<items.length-1?'1px solid var(--line)':'none',
                background:'transparent', cursor:'pointer'
              }}
              onMouseEnter={e=> e.currentTarget.style.background='#F8FAFC'}
              onMouseLeave={e=> e.currentTarget.style.background='transparent'}>
                <div style={{display:'flex', alignItems:'flex-start', gap:10}}>
                  <span style={{width:8, height:8, borderRadius:'50%', background:dotColor, marginTop:6, flexShrink:0}}/>
                  <div style={{flex:1, minWidth:0}}>
                    <div style={{fontSize:12.5, fontWeight:600, color:'var(--ink)', lineHeight:1.4}}>{it.title}</div>
                    <div style={{fontSize:11.5, color:'var(--ink-3)', marginTop:3, lineHeight:1.5}}>{it.body}</div>
                    <div style={{fontSize:10.5, color:'var(--ink-4)', marginTop:4}}>{it.date}</div>
                  </div>
                </div>
              </button>
            );
          })}
        </div>
        <div style={{padding:'10px 18px', borderTop:'1px solid var(--line)', textAlign:'center'}}>
          <button onClick={()=>{ go('upper'); onClose(); }} style={{fontSize:12, color:'var(--blue)', background:'transparent', border:'none', fontWeight:600, cursor:'pointer'}}>알림 설정 ›</button>
        </div>
      </div>
    </div>
  );
}

// ─────────── Confirm-style modal for actions ───────────
function ActionModal({ open, title, body, confirmLabel, onConfirm, onClose, tone='info' }){
  if(!open) return null;
  const headerBg = tone==='warn' ? '#FBEFD9' : tone==='success' ? '#DBEEEA' : '#E6EEFB';
  const headerFg = tone==='warn' ? '#8B5410' : tone==='success' ? '#155F53' : '#0B2E5C';
  return (
    <div onClick={onClose} style={{
      position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:9999,
      display:'flex', alignItems:'center', justifyContent:'center', padding:24,
      backdropFilter:'blur(4px)'
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background:'#fff', borderRadius:12, width:'min(480px, 100%)', overflow:'hidden',
        boxShadow:'0 24px 60px rgba(15,23,42,0.28)'
      }}>
        <div style={{padding:'14px 22px', background:headerBg, color:headerFg, fontSize:11, fontWeight:700, letterSpacing:'0.06em', textTransform:'uppercase'}}>확인</div>
        <div style={{padding:'22px 24px'}}>
          <div style={{fontSize:16, fontWeight:700, color:'var(--ink)', marginBottom:8}}>{title}</div>
          <div style={{fontSize:13, color:'var(--ink-2)', lineHeight:1.7}}>{body}</div>
        </div>
        <div style={{padding:'12px 22px', borderTop:'1px solid var(--line)', display:'flex', justifyContent:'flex-end', gap:8, background:'#FBFCFE'}}>
          <button onClick={onClose} style={{padding:'8px 14px', fontSize:12, fontWeight:600, color:'var(--ink-2)', background:'#fff', border:'1px solid var(--line-2)', borderRadius:7, cursor:'pointer'}}>취소</button>
          <button onClick={()=>{ onConfirm(); onClose(); }} style={{padding:'8px 14px', fontSize:12, fontWeight:600, color:'#fff', background:'var(--navy)', border:'1px solid var(--navy)', borderRadius:7, cursor:'pointer'}}>{confirmLabel || '확인'}</button>
        </div>
      </div>
    </div>
  );
}

// ─────────── Evidence / detail modals ───────────
const _modalListeners = [];
window.openModal = function(payload){ _modalListeners.forEach(fn => fn(payload)); };

function ModalHost(){
  const [m, setM] = useStateW(null);
  useEffectW(()=>{
    const fn = p => setM(p);
    _modalListeners.push(fn);
    const onKey = e => { if(e.key === 'Escape') setM(null); };
    document.addEventListener('keydown', onKey);
    return ()=>{
      document.removeEventListener('keydown', onKey);
      const i = _modalListeners.indexOf(fn); if(i>=0) _modalListeners.splice(i,1);
    };
  },[]);
  if(!m) return null;
  return (
    <div onClick={()=>setM(null)} style={{
      position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:9999,
      display:'flex', alignItems:'center', justifyContent:'center', padding:24,
      backdropFilter:'blur(4px)'
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background:'#fff', borderRadius:12, width:'min(720px, 100%)', maxHeight:'85vh',
        overflow:'hidden', boxShadow:'0 24px 60px rgba(15,23,42,0.28)',
        display:'flex', flexDirection:'column'
      }}>
        <div style={{padding:'14px 22px', background:m.headerBg||'#E6EEFB', color:m.headerFg||'#0B2E5C', display:'flex', alignItems:'center', justifyContent:'space-between'}}>
          <div>
            <div style={{fontSize:10.5, fontWeight:700, letterSpacing:'0.08em', textTransform:'uppercase', opacity:.78}}>{m.eyebrow||'상세'}</div>
            <div style={{fontSize:15, fontWeight:700, marginTop:2}}>{m.title}</div>
          </div>
          <button onClick={()=>setM(null)} style={{background:'rgba(255,255,255,0.4)', border:'none', width:28, height:28, borderRadius:6, cursor:'pointer', fontSize:14}}>×</button>
        </div>
        <div style={{padding:'20px 24px', overflowY:'auto', flex:1, fontSize:13, lineHeight:1.7, color:'var(--ink-2)'}}>
          {m.body}
        </div>
        <div style={{padding:'12px 22px', borderTop:'1px solid var(--line)', display:'flex', justifyContent:'flex-end', gap:8, background:'#FBFCFE'}}>
          {(m.actions || [{label:'닫기', primary:true, onClick:()=>setM(null)}]).map((a,i)=>(
            <button key={i} onClick={()=>{ if(a.onClick) a.onClick(); setM(null); }} style={{
              padding:'8px 14px', fontSize:12, fontWeight:600, borderRadius:7, cursor:'pointer',
              color: a.primary?'#fff':'var(--ink-2)',
              background: a.primary?'var(--navy)':'#fff',
              border:'1px solid '+(a.primary?'var(--navy)':'var(--line-2)')
            }}>{a.label}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────────── Report generation loading modal ───────────
function ReportLoadingHost(){
  const [stage, setStage] = useStateW(-1);
  const stages = [
    { label:'법령 본문 임베딩 매칭 중', sub:'Claude·GPT-4o + KoBERT 법률 임베딩 모델 로드' },
    { label:'하위규정 영향도 계산 중',  sub:'관련 시행령·고시·자치법규 12,847건 스캔' },
    { label:'정비 우선순위 산정 중',    sub:'영향 0.4 · 민원 0.3 · 제보 0.3 가중 가중치 적용' },
    { label:'리포트 작성 중',           sub:'발행번호 RSR-2026-Q1-014 · PDF 생성' },
  ];
  useEffectW(()=>{
    const fn = ()=>{
      setStage(0);
      let i = 0;
      const tick = ()=>{
        i++;
        if(i < stages.length){ setStage(i); setTimeout(tick, 800); }
        else { setTimeout(()=>{
          setStage(-1);
          window.toast && window.toast('Sync Law 분석 리포트가 생성되었습니다', {tone:'success', sub:'발행번호 RSR-2026-Q1-014 · 인사이트 화면에서 확인'});
          if(window.__goReport) window.__goReport();
        }, 700); }
      };
      setTimeout(tick, 800);
    };
    window.__startReportGen = fn;
    return ()=>{ delete window.__startReportGen; };
  },[]);
  if(stage < 0) return null;
  return (
    <div style={{
      position:'fixed', inset:0, background:'rgba(11,46,92,0.62)', zIndex:9999,
      display:'flex', alignItems:'center', justifyContent:'center', backdropFilter:'blur(6px)'
    }}>
      <div style={{background:'#fff', borderRadius:14, width:'min(520px,92vw)', padding:'28px 32px', boxShadow:'0 24px 60px rgba(15,23,42,0.32)'}}>
        <div style={{display:'flex', alignItems:'center', gap:10, marginBottom:6}}>
          <div style={{width:32, height:32, borderRadius:8, background:'linear-gradient(135deg,#0B2E5C,#2E5FB0)', display:'flex', alignItems:'center', justifyContent:'center'}}>
            <Icon name="brain" size={16} color="#5EE6B5"/>
          </div>
          <div>
            <div style={{fontSize:10, fontWeight:700, color:'var(--ink-3)', letterSpacing:'0.08em'}}>SYNC·LAW AI · 분석 리포트 생성</div>
            <div style={{fontSize:14, fontWeight:700, color:'var(--ink)'}}>{stages[stage].label}</div>
          </div>
        </div>
        <div style={{fontSize:11.5, color:'var(--ink-3)', marginBottom:14}}>{stages[stage].sub}</div>
        <div style={{height:6, background:'#EEF2F7', borderRadius:99, overflow:'hidden', marginBottom:18}}>
          <div style={{height:'100%', width:`${((stage+1)/stages.length)*100}%`, background:'linear-gradient(90deg,#2E5FB0,#1F7A6B)', transition:'width 0.6s'}}/>
        </div>
        <div style={{display:'grid', gridTemplateColumns:'repeat(4, 1fr)', gap:6}}>
          {stages.map((s,i)=>(
            <div key={i} style={{
              padding:'7px 6px', borderRadius:6, fontSize:9.5, textAlign:'center',
              background: i<=stage? 'var(--blue-soft)':'#F8FAFC',
              color: i<=stage? 'var(--blue)':'var(--ink-4)',
              fontWeight: i===stage? 700:500,
              border:'1px solid '+(i===stage?'var(--blue)':'var(--line)')
            }}>{`0${i+1}`} {s.label.split(' ')[0]}</div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ─────────── Citizen report AI analysis result ───────────
function CitizenAnalysisModal(){
  const [open, setOpen] = useStateW(false);
  const [phase, setPhase] = useStateW(0); // 0 loading, 1 result
  useEffectW(()=>{
    window.__runCitizenAnalysis = ()=>{
      setOpen(true); setPhase(0);
      setTimeout(()=>setPhase(1), 2200);
    };
    return ()=>{ delete window.__runCitizenAnalysis; };
  },[]);
  if(!open) return null;
  return (
    <div onClick={()=>setOpen(false)} style={{
      position:'fixed', inset:0, background:'rgba(15,23,42,0.55)', zIndex:9999,
      display:'flex', alignItems:'center', justifyContent:'center', padding:24, backdropFilter:'blur(4px)'
    }}>
      <div onClick={e=>e.stopPropagation()} style={{background:'#fff', borderRadius:12, width:'min(680px,100%)', maxHeight:'85vh', overflow:'hidden', display:'flex', flexDirection:'column'}}>
        <div style={{padding:'14px 22px', background:'#0B2E5C', color:'#fff', display:'flex', justifyContent:'space-between', alignItems:'center'}}>
          <div>
            <div style={{fontSize:10, fontWeight:700, letterSpacing:'0.08em', opacity:.7}}>AI 구조화 분석</div>
            <div style={{fontSize:15, fontWeight:700, marginTop:2}}>국민 제보 자동 매칭 결과</div>
          </div>
          <button onClick={()=>setOpen(false)} style={{background:'rgba(255,255,255,0.18)', border:'none', color:'#fff', width:28, height:28, borderRadius:6, cursor:'pointer', fontSize:14}}>×</button>
        </div>
        {phase === 0 ? (
          <div style={{padding:'40px 32px', textAlign:'center', color:'var(--ink-2)'}}>
            <div style={{display:'inline-block', width:36, height:36, border:'3px solid #E6EEFB', borderTopColor:'#2E5FB0', borderRadius:'50%', animation:'spin 1s linear infinite'}}/>
            <div style={{fontSize:14, fontWeight:600, marginTop:14}}>관련 법령·서식·안내문 탐색 중</div>
            <div style={{fontSize:12, color:'var(--ink-3)', marginTop:4}}>의미 유사도 0.82 이상 매칭만 표시 · 평균 1.8초</div>
          </div>
        ) : (
          <div style={{padding:'20px 24px', overflowY:'auto', flex:1}}>
            <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:14, padding:'10px 12px', background:'#DBEEEA', borderRadius:8}}>
              <Icon name="check" size={14} color="#1F7A6B"/>
              <span style={{fontSize:12.5, color:'#155F53', fontWeight:600}}>유사 사례 4건 매칭 · 담당기관 자동 추정 완료</span>
            </div>
            {[
              { type:'법령',  sim:94, src:'개인정보 보호법 시행령 제5조 제1항',  agency:'중앙위원회 K' },
              { type:'서식',  sim:88, src:'주민등록표 등본 교부신청서 별지 제3호', agency:'H부처' },
              { type:'안내문',sim:81, src:'민원24 통합 안내 5장',                 agency:'H부처' },
              { type:'조례',  sim:76, src:'광역시 X 정보화기본조례 제12조',        agency:'광역시 X' },
            ].map((r,i)=>(
              <div key={i} style={{padding:'12px 14px', borderRadius:8, border:'1px solid var(--line)', marginBottom:8, display:'flex', alignItems:'center', gap:12}}>
                <span style={{fontSize:10, fontWeight:700, color:'var(--blue)', background:'var(--blue-soft)', padding:'3px 8px', borderRadius:99, minWidth:42, textAlign:'center'}}>{r.type}</span>
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontSize:13, fontWeight:600, color:'var(--ink)'}}>{r.src}</div>
                  <div style={{fontSize:11, color:'var(--ink-3)', marginTop:3}}>{r.agency} · 의미 유사도 <span className="mono" style={{fontWeight:700, color:'var(--blue)'}}>{r.sim}%</span></div>
                </div>
              </div>
            ))}
            <div style={{padding:'10px 12px', background:'#FBEFD9', color:'#8B5410', borderRadius:8, fontSize:12, marginTop:6}}>
              <strong>추정 담당기관:</strong> H부처 정보공개정책과 · 7일 이내 회신 기한
            </div>
          </div>
        )}
        <div style={{padding:'12px 22px', borderTop:'1px solid var(--line)', display:'flex', justifyContent:'flex-end', gap:8, background:'#FBFCFE'}}>
          <button onClick={()=>setOpen(false)} style={{padding:'8px 14px', fontSize:12, fontWeight:600, borderRadius:7, cursor:'pointer', color:'var(--ink-2)', background:'#fff', border:'1px solid var(--line-2)'}}>닫기</button>
          {phase === 1 && <button onClick={()=>{ setOpen(false); window.toast('담당기관에 자동 이송되었습니다',{tone:'success',sub:'AI 검증 결과 첨부 · 14일 이내 회신 예정'}); }} style={{padding:'8px 14px', fontSize:12, fontWeight:600, borderRadius:7, cursor:'pointer', color:'#fff', background:'var(--navy)', border:'1px solid var(--navy)'}}>담당기관 이송</button>}
        </div>
      </div>
    </div>
  );
}

// ─────────── Global click delegator ───────────
// Maps data-action values to toast/modal feedback for buttons that don't yet have onClick.
const ACTION_FEEDBACK = {
  'export-report-csv':            { msg:'CSV 내보내기를 준비 중입니다',    sub:'다운로드 폴더에 저장됩니다',     tone:'info' },
  'configure-alerts':             { msg:'알림 설정 화면 준비 중',         sub:'기관별 · 영향도별 알림을 구성합니다', tone:'info' },
  'recalculate-impact':           { msg:'영향도 재계산을 시작했습니다',    sub:'예상 소요 시간 약 4분',          tone:'info' },
  'submit-citizen-report':        { msg:'제보가 접수되었습니다',          sub:'AI 검증 후 담당기관에 이송됩니다', tone:'success' },
  'save-report-draft':            { msg:'임시 저장되었습니다',            sub:'24시간 동안 보관됩니다',          tone:'success' },
  'select-detected-amendment':    { msg:'개정 사항을 불러오는 중입니다',   sub:null,                              tone:'info' },
  'open-compare-view':            { msg:'신구조문 비교 화면으로 이동합니다', sub:null,                            tone:'info' },
  'open-ai-analysis':             { msg:'AI 분석 결과를 불러옵니다',      sub:null,                              tone:'info' },
  'insert-example-report':        { msg:'예시 제보를 입력란에 채웠습니다',  sub:null,                              tone:'success' },
  'input-citizen-report':         null, // text input, never feedback
  'open-advanced-filter':         { msg:'고급 필터 패널을 열었습니다', sub:'유형·기관·위험도별로 결과를 좁힐 수 있습니다', tone:'info' },
  'export-comparison-table':      { msg:'대조표 내보내기를 준비 중입니다', sub:'엑셀·CSV 형식으로 저장할 수 있습니다', tone:'success' },
  'open-form-category':           { msg:'서식 카테고리 필터를 준비 중입니다', sub:'민원서식·안내문·체크리스트별 분류', tone:'info' },
  'send-maintenance-recommendation': { msg:'정비 권고서 발송을 준비 중입니다', sub:'선택한 기관으로 시범 발송 흐름을 실행합니다', tone:'success' },
};

// Modal-based handlers for evidence/detail buttons
const ACTION_MODALS = {
  'export-evidence-pdf': () => window.openModal({
    eyebrow:'근거 자료', title:'AI 분석 근거 자료 패키지',
    body: (
      <div>
        <div style={{fontSize:12.5, color:'var(--ink-3)', marginBottom:14}}>다음 4건의 문서가 분석 근거로 사용되었습니다. PDF 다운로드는 데모 환경에서 비활성화되어 있습니다.</div>
        {[
          {n:'01', t:'개인정보 보호법 제15조 신구조문 대비표', s:'국가법령정보센터 · 2.1MB'},
          {n:'02', t:'관련 시행령·고시 영향도 분석 (12건)',     s:'AI 자동 생성 · 4.8MB'},
          {n:'03', t:'유사 자치법규 매칭 리스트 (47건)',         s:'자치법규정보시스템 · 1.3MB'},
          {n:'04', t:'민원·국민 제보 통계 부록',                 s:'최근 90일 · 0.7MB'},
        ].map((r,i)=>(
          <div key={i} style={{display:'flex', alignItems:'center', gap:12, padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, marginBottom:6}}>
            <span className="mono" style={{fontSize:11, color:'var(--ink-4)', minWidth:24}}>{r.n}</span>
            <div style={{flex:1}}>
              <div style={{fontSize:13, fontWeight:600, color:'var(--ink)'}}>{r.t}</div>
              <div style={{fontSize:11, color:'var(--ink-3)', marginTop:2}}>{r.s}</div>
            </div>
            <Icon name="download" size={14} color="var(--ink-4)"/>
          </div>
        ))}
      </div>
    )
  })
};

const DAY4_SIGNAL_DEFS = [
  { key:'numericMismatch', label:'숫자 불일치', tone:'#C77A1B', soft:'#FBEFD9', group:'A' },
  { key:'periodMismatch', label:'기간 불일치', tone:'#C77A1B', soft:'#FBEFD9', group:'A' },
  { key:'authorityMismatch', label:'권한자 불일치', tone:'#C77A1B', soft:'#FBEFD9', group:'A' },
  { key:'repealedCitation', label:'폐지법령 인용', tone:'#C77A1B', soft:'#FBEFD9', group:'A' },
  { key:'effectiveDateMismatch', label:'시행일 불일치', tone:'#C77A1B', soft:'#FBEFD9', group:'A' },
  { key:'bClassification', label:'B 의도 존치', tone:'#2E5FB0', soft:'var(--blue-soft)', group:'B' }
];

function syncNum(value){
  const num = Number(value || 0);
  return Number.isFinite(num) ? num : 0;
}

function syncFmt(value){
  return syncNum(value).toLocaleString();
}

function syncDistance(value){
  const num = Number(value);
  return Number.isFinite(num) ? num.toFixed(5) : '-';
}

function readStoredAnalysisOrdinance(){
  try {
    const raw = sessionStorage.getItem('syncLaw.analysisOrdinance');
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null;
  }
}

function syncActionContext(btn){
  const stored = readStoredAnalysisOrdinance();
  const card = btn?.closest?.('[data-day4-ordinance-id], [data-residual-hit-id]');
  const docId = btn?.dataset?.docId || card?.dataset?.day4OrdinanceId || stored?.ordinanceId || stored?.id || '';
  const mst = btn?.dataset?.docMst || card?.dataset?.day4Mst || stored?.mst || '';
  const titleHint = btn?.dataset?.docTitle || stored?.title || '';
  const agencyHint = btn?.dataset?.docAgency || card?.dataset?.agency || stored?.agency || '';
  const day4Items = window.__syncDay4Payload?.ordinances || [];
  const anomalyItems = window.__syncAnomalyScores?.ordinanceSummaries || [];
  const reviewItems = window.__syncLevel4Reviews?.reviews || [];
  const fallbackTop = window.RSData?.DAY5_DEFAULT_METRICS?.topAnomalies || [];

  const matchById = (item) =>
    item &&
    ((docId && (item.id === docId || item.ordinanceId === docId)) ||
      (mst && item.mst === mst) ||
      (titleHint && item.title === titleHint));

  const ord = day4Items.find(matchById) || null;
  const anomaly =
    anomalyItems.find(matchById) ||
    fallbackTop.find(matchById) ||
    null;
  const review = reviewItems.find(matchById) || null;
  const title = ord?.title || anomaly?.title || review?.title || titleHint || '선택 자치법규';
  const agency = ord?.agency || anomaly?.agency || review?.agency || agencyHint || '소관기관 확인 필요';
  const resolvedId = ord?.id || anomaly?.ordinanceId || review?.ordinanceId || docId || '';
  const resolvedMst = ord?.mst || anomaly?.mst || review?.mst || mst || '';

  return { btn, card, docId:resolvedId, mst:resolvedMst, title, agency, ord, anomaly, review };
}

function signalRows(ord){
  return DAY4_SIGNAL_DEFS.map((def) => {
    const signal = ord?.signals?.[def.key] || {};
    return {
      ...def,
      count: syncNum(signal.count),
      samples: Array.isArray(signal.samples) ? signal.samples : []
    };
  });
}

function syncMiniBadge(label, tone){
  const colors = {
    amber: { bg:'#FBEFD9', fg:'#8B5410', bd:'#EAC98D' },
    blue: { bg:'#E6EEFB', fg:'#0B2E5C', bd:'#BFD0EE' },
    teal: { bg:'#DBEEEA', fg:'#155F53', bd:'#B7D8D0' },
    ghost: { bg:'#F8FAFC', fg:'var(--ink-3)', bd:'var(--line)' }
  };
  const c = colors[tone] || colors.ghost;
  return <span style={{display:'inline-flex', alignItems:'center', padding:'2px 8px', borderRadius:99, background:c.bg, color:c.fg, border:`1px solid ${c.bd}`, fontSize:10.5, fontWeight:700}}>{label}</span>;
}

function openDay4SourceModal(btn){
  const ctx = syncActionContext(btn);
  const rows = signalRows(ctx.ord);
  const samples = rows.flatMap((row) => row.samples.slice(0, 2).map((text) => ({ label:row.label, text }))).slice(0, 4);
  window.openModal({
    eyebrow:'원문 근거',
    title:ctx.title,
    headerBg:'#E6EEFB',
    headerFg:'#0B2E5C',
    body: (
      <div>
        <div style={{display:'flex', gap:8, flexWrap:'wrap', marginBottom:12}}>
          {syncMiniBadge(ctx.agency, 'blue')}
          {ctx.mst && syncMiniBadge(`MST ${ctx.mst}`, 'ghost')}
          {ctx.ord?.flaggedCandidates > 0 ? syncMiniBadge(`A 후보 +${ctx.ord.flaggedCandidates}`, 'amber') : syncMiniBadge('표본 확인', 'teal')}
        </div>
        <div style={{display:'grid', gridTemplateColumns:'120px 1fr', rowGap:7, fontSize:12.5, padding:'14px 16px', background:'#F8FAFC', border:'1px solid var(--line)', borderRadius:8, marginBottom:12}}>
          <span style={{color:'var(--ink-3)'}}>대상 문서</span><span style={{fontWeight:700, color:'var(--ink)'}}>{ctx.title}</span>
          <span style={{color:'var(--ink-3)'}}>소관기관</span><span>{ctx.agency}</span>
          <span style={{color:'var(--ink-3)'}}>본문 길이</span><span className="mono">{syncFmt(ctx.ord?.articleLength)}자</span>
          <span style={{color:'var(--ink-3)'}}>부칙 길이</span><span className="mono">{syncFmt(ctx.ord?.addendumLength)}자</span>
          <span style={{color:'var(--ink-3)'}}>조회 기준</span><span>법제처 자치법규 본문 MST 기준</span>
        </div>
        {samples.length > 0 ? (
          <div style={{display:'grid', gap:8}}>
            {samples.map((sample, i) => (
              <div key={i} style={{padding:'10px 12px', border:'1px solid var(--line)', borderRadius:8, background:'#fff'}}>
                <div style={{fontSize:10.5, color:'var(--amber)', fontWeight:800, marginBottom:4}}>{sample.label}</div>
                <div style={{fontSize:12, color:'var(--ink-2)', lineHeight:1.65}}>{sample.text}</div>
              </div>
            ))}
          </div>
        ) : (
          <div style={{padding:'12px 14px', background:'#FBFCFE', border:'1px solid var(--line)', borderRadius:8, fontSize:12, color:'var(--ink-3)', lineHeight:1.6}}>
            이 카드는 전체 모드의 메타 조회 항목입니다. 본문 기반 룰 신호는 표본 모드에서 확인한 18개 자치법규에만 표시합니다.
          </div>
        )}
      </div>
    ),
    actions:[{label:'닫기', primary:true}]
  });
}

function openDay4EvidenceModal(btn){
  const ctx = syncActionContext(btn);
  const rows = signalRows(ctx.ord);
  const aCount = rows.filter((row) => row.group === 'A').reduce((sum, row) => sum + row.count, 0);
  const worst = ctx.anomaly?.worstChunk || {};
  const reviewText = ctx.review?.review;
  window.openModal({
    eyebrow:'분석 근거',
    title:`${ctx.title} · Level 1-4 근거`,
    headerBg:'#DBEEEA',
    headerFg:'#155F53',
    body: (
      <div>
        <div style={{display:'grid', gridTemplateColumns:'repeat(3, 1fr)', gap:8, marginBottom:14}}>
          <div style={{padding:'10px 12px', background:'#FBEFD9', border:'1px solid #EAC98D', borderRadius:8}}>
            <div style={{fontSize:10.5, color:'#8B5410', fontWeight:700}}>A 룰 신호</div>
            <div className="mono" style={{fontSize:22, color:'#C77A1B', fontWeight:800}}>{syncFmt(aCount)}</div>
          </div>
          <div style={{padding:'10px 12px', background:'var(--blue-soft)', border:'1px solid #BFD0EE', borderRadius:8}}>
            <div style={{fontSize:10.5, color:'#0B2E5C', fontWeight:700}}>B 차감 마커</div>
            <div className="mono" style={{fontSize:22, color:'#2E5FB0', fontWeight:800}}>{syncFmt(rows.find((row) => row.key === 'bClassification')?.count)}</div>
          </div>
          <div style={{padding:'10px 12px', background:'#F8FAFC', border:'1px solid var(--line)', borderRadius:8}}>
            <div style={{fontSize:10.5, color:'var(--ink-3)', fontWeight:700}}>Level 2 거리</div>
            <div className="mono" style={{fontSize:22, color:'var(--ink)', fontWeight:800}}>{syncDistance(worst.minDistance)}</div>
          </div>
        </div>
        <div style={{display:'grid', gridTemplateColumns:'repeat(3, 1fr)', gap:8, marginBottom:14}}>
          {rows.map((row) => (
            <div key={row.key} style={{padding:'9px 10px', background:row.soft, border:`1px solid ${row.tone}33`, borderRadius:8}}>
              <div style={{fontSize:10.5, color:'var(--ink-3)', fontWeight:700}}>{row.label}</div>
              <div className="mono" style={{fontSize:17, color:row.tone, fontWeight:800}}>{syncFmt(row.count)}</div>
            </div>
          ))}
        </div>
        <div style={{padding:'12px 14px', border:'1px solid var(--line)', borderRadius:8, marginBottom:10}}>
          <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:800, marginBottom:6}}>Level 2 · 가장 멀어진 신호</div>
          <div style={{fontSize:13, color:'var(--ink)', fontWeight:700}}>{worst.errorType || ctx.anomaly?.worstErrorType || '거리 데이터 로드 전'}</div>
          {(worst.nearest || []).slice(0, 3).map((n, i) => (
            <div key={n.id || i} style={{display:'grid', gridTemplateColumns:'24px 1fr 64px', gap:10, alignItems:'start', marginTop:8, fontSize:11.5}}>
              <span className="mono" style={{color:'var(--ink-4)'}}>#{i+1}</span>
              <span style={{color:'var(--ink-2)'}}>{n.case_name} · p.{n.page}</span>
              <span className="mono" style={{textAlign:'right', color:'var(--amber)', fontWeight:700}}>{syncDistance(n.distance)}</span>
            </div>
          ))}
        </div>
        <div style={{padding:'12px 14px', background:'#FBFCFE', border:'1px solid var(--line)', borderRadius:8, fontSize:12.2, color:'var(--ink-2)', lineHeight:1.65}}>
          <div style={{fontSize:11, color:'var(--ink-3)', fontWeight:800, marginBottom:5}}>Level 4 · 검토사유</div>
          {reviewText ? (
            <>
              <div><b>관찰</b> · {reviewText.observation}</div>
              <div><b>근거</b> · {reviewText.evidence}</div>
              <div><b>권고</b> · {reviewText.recommendation}</div>
            </>
          ) : 'Level 4 검토사유는 분석 상세 화면에서 이 조례를 선택하면 함께 표시됩니다.'}
        </div>
      </div>
    ),
    actions:[{label:'닫기', primary:true}]
  });
}

function openDay4ReviewRequestModal(btn){
  const ctx = syncActionContext(btn);
  const rows = signalRows(ctx.ord);
  const aCount = rows.filter((row) => row.group === 'A').reduce((sum, row) => sum + row.count, 0);
  const due = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000);
  const dueText = `${due.getFullYear()}-${String(due.getMonth() + 1).padStart(2, '0')}-${String(due.getDate()).padStart(2, '0')}`;
  window.openModal({
    eyebrow:'소관부처 검토 요청',
    title:`${ctx.agency} 검토 요청 초안`,
    headerBg:'#FBEFD9',
    headerFg:'#8B5410',
    body: (
      <div>
        <div style={{fontSize:13, lineHeight:1.7, marginBottom:14}}>
          실제 발송 전 검토용 초안입니다. 선택한 Day 4/5 자치법규 산출물을 기준으로 대상 기관과 사유를 구성했습니다.
        </div>
        <div style={{display:'grid', gridTemplateColumns:'120px 1fr', rowGap:8, fontSize:12.5, padding:'14px 16px', background:'#F8FAFC', border:'1px solid var(--line)', borderRadius:8}}>
          <span style={{color:'var(--ink-3)'}}>대상 기관</span><span style={{fontWeight:700}}>{ctx.agency}</span>
          <span style={{color:'var(--ink-3)'}}>대상 조례</span><span>{ctx.title}</span>
          <span style={{color:'var(--ink-3)'}}>식별자</span><span className="mono">{ctx.docId || '-'} / MST {ctx.mst || '-'}</span>
          <span style={{color:'var(--ink-3)'}}>검토 사유</span><span>A 룰 신호 {syncFmt(aCount)}건 · 최장거리 신호 {ctx.anomaly?.worstChunk?.errorType || ctx.review?.worstErrorType || '확인 필요'}</span>
          <span style={{color:'var(--ink-3)'}}>회신 기한</span><span className="mono" style={{color:'var(--amber)', fontWeight:800}}>{dueText} (D-14)</span>
        </div>
      </div>
    ),
    actions:[
      {label:'취소'},
      {label:'초안 생성 확인', primary:true, onClick:()=>window.toast(`${ctx.agency} 검토 요청 초안이 생성되었습니다`, {tone:'success', sub:`${ctx.title} · 회신 기한 ${dueText}`})}
    ]
  });
}

Object.assign(ACTION_MODALS, {
  'open-original-source': openDay4SourceModal,
  'show-sync-evidence': openDay4EvidenceModal,
  'request-agency-review': openDay4ReviewRequestModal
});

window.__syncHandleActionButton = function(btn){
  const action = btn?.dataset?.action;
  if (!action || !ACTION_MODALS[action]) return false;
  ACTION_MODALS[action](btn);
  return true;
};

function GlobalClickDelegator(){
  useEffectW(()=>{
    const handler = (e) => {
      const btn = e.target.closest('button[data-action], a[data-action]');
      if(!btn) return;
      if(btn.dataset._handled === '1') return;
      const action = btn.dataset.action;

      // 화면 이동용 메뉴/탭 클릭은 알림을 띄우지 않습니다.
      // 기존에는 open-dashboard 같은 data-action까지 기본 토스트가 떠서
      // 좌측 메뉴를 누를 때마다 '처리 중' 알림이 과하게 노출되었습니다.
      const silentNavActions = new Set([
        'open-home','open-dashboard','open-upper','open-compare','open-lower',
        'open-forms','open-report','open-analysis','open-insight','open-agencies'
      ]);
      if (btn.dataset.navId || silentNavActions.has(action)) return;

      // Special: report generation loading flow
      if(action === 'generate-sync-law-report'){
        e.preventDefault(); e.stopPropagation();
        if(window.__startReportGen) window.__startReportGen();
        return;
      }
      // Special: citizen report analysis flow
      if(action === 'run-citizen-report-analysis'){
        e.preventDefault();
        if(window.__runCitizenAnalysis) window.__runCitizenAnalysis();
        return;
      }
      // Modal-based actions
      if(ACTION_MODALS[action]){
        e.preventDefault(); e.stopPropagation();
        window.__syncHandleActionButton(btn);
        return;
      }

      const fb = ACTION_FEEDBACK[action];
      if(fb === null) return;
      if(fb){
        window.toast(fb.msg, { tone: fb.tone, sub: fb.sub });
        return;
      }
      const label = btn.textContent.trim().replace(/\s+/g,' ').slice(0,40) || action;
      window.toast(label + ' — 처리 중', { tone:'info', sub:'시연용 데모 화면입니다' });
    };
    document.addEventListener('click', handler);
    return ()=> document.removeEventListener('click', handler);
  },[]);
  return null;
}

// ─────────── Inject animations CSS once ───────────
(function injectCSS(){
  if(document.getElementById('rs-wires-css')) return;
  const css = document.createElement('style');
  css.id = 'rs-wires-css';
  css.textContent = `
    @keyframes slideInRight { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
    @keyframes slideInDown  { from { transform: translateY(-10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
    @keyframes spin { to { transform: rotate(360deg); } }
    @keyframes fadeUp { from { opacity:0; transform: translateY(8px);} to { opacity:1; transform: translateY(0);} }
    @keyframes drawLine { from { stroke-dashoffset: 1000; } to { stroke-dashoffset: 0; } }
    main > * { animation: fadeUp 0.5s cubic-bezier(0.16,1,0.3,1) both; }
    main [data-stagger] > * { animation: fadeUp 0.5s cubic-bezier(0.16,1,0.3,1) both; }
    main [data-stagger] > *:nth-child(1){ animation-delay: 0.04s; }
    main [data-stagger] > *:nth-child(2){ animation-delay: 0.08s; }
    main [data-stagger] > *:nth-child(3){ animation-delay: 0.12s; }
    main [data-stagger] > *:nth-child(4){ animation-delay: 0.16s; }
    main [data-stagger] > *:nth-child(5){ animation-delay: 0.20s; }
    main [data-stagger] > *:nth-child(6){ animation-delay: 0.24s; }
    button:active { transform: translateY(0.5px); }
    button:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
  `;
  document.head.appendChild(css);
})();

Object.assign(window, { ToastHost, SearchPalette, NotificationsPanel, ActionModal, GlobalClickDelegator, ModalHost, ReportLoadingHost, CitizenAnalysisModal });
