// Report.jsx — 보고서 생성 탭. 좌: 보고서 리스트 / 새 보고서 폼 · 우: 보고서 조회·편집.

// ---------------------------------------------------------------- form primitives
function Field({ label, hint, children, req }) {
  return (
    <label style={{ display: 'block', textAlign: 'left' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 6, marginBottom: 7 }}>
        <span style={{ fontFamily: T.font, fontWeight: 700, fontSize: 12, color: T.c700 }}>{label}</span>
        {req && <span style={{ color: T.alert, fontSize: 12, fontWeight: 700 }}>*</span>}
        {hint && <span style={{ fontFamily: T.font, fontWeight: 500, fontSize: 11, color: T.c400, marginLeft: 'auto' }}>{hint}</span>}
      </div>
      {children}
    </label>);
}

// custom dropdown select. options:[{v,l,sub}]
function RSelect({ value, onChange, options, placeholder = '선택', icon }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);
  const sel = options.find((o) => o.v === value);
  const T_POP = '0 8px 28px rgba(15,23,34,.16)';
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen((o) => !o)} style={{
        width: '100%', display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer',
        background: T.white, border: '1px solid ' + (open ? T.blue : T.c250), borderRadius: 8, padding: '9px 11px',
        boxShadow: open ? '0 0 0 3px ' + T.blueSurface : 'none', transition: 'border-color .12s, box-shadow .12s' }}>
        {icon && <Icon name={icon} size={15} color={T.c400} />}
        <span style={{ flex: 1, textAlign: 'left', fontFamily: T.font, fontWeight: 600, fontSize: 13,
          color: sel ? T.c800 : T.c400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sel ? sel.l : placeholder}</span>
        <Icon name="chevDown" size={15} color={T.c400} stroke={2.2} style={{ transition: 'transform .15s', transform: open ? 'rotate(180deg)' : 'none' }} />
      </button>
      {open &&
        <div style={{ position: 'absolute', zIndex: 40, top: 'calc(100% + 5px)', left: 0, right: 0, background: T.white,
          border: '1px solid ' + T.c200, borderRadius: 10, boxShadow: T_POP, padding: 5, maxHeight: 280, overflowY: 'auto' }}>
          {options.map((o) => {
            const on = o.v === value;
            return (
              <button key={o.v} type="button" onClick={() => { onChange(o.v); setOpen(false); }} style={{
                width: '100%', display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', textAlign: 'left',
                background: on ? T.blueSurface : 'transparent', border: 0, borderRadius: 7, padding: '8px 9px' }}
                onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = T.c100; }}
                onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = 'transparent'; }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontFamily: T.font, fontWeight: on ? 700 : 600, fontSize: 13, color: on ? T.blue600 : T.c800 }}>{o.l}</div>
                  {o.sub && <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 11, color: T.c400, marginTop: 1 }}>{o.sub}</div>}
                </div>
                {on && <Icon name="check" size={15} color={T.blue} stroke={2.6} />}
              </button>);
          })}
        </div>}
    </div>);
}

const R_POP = '0 8px 28px rgba(15,23,34,.16)';

// searchable ledger / item picker keyed on product type
function LedgerPicker({ product, value, onChange }) {
  const [open, setOpen] = React.useState(false);
  const [q, setQ] = React.useState('');
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);
  const items = React.useMemo(() => {
    if (product === '주식') return STOCKS.map((s) => ({ code: s.ticker, ledger: s.ticker, name: s.name, sub: s.sector }));
    return BONDS.map((b) => ({ code: b.isin, ledger: b.ledger, name: b.name, sub: b.cat + ' · ' + b.ccy }));
  }, [product]);
  const filtered = React.useMemo(() => {
    if (!q) return items;
    const s = q.toLowerCase();
    return items.filter((i) => i.name.toLowerCase().includes(s) || i.code.toLowerCase().includes(s) || String(i.ledger).toLowerCase().includes(s));
  }, [items, q]);
  const sel = items.find((i) => i.code === value);
  const codeLabel = product === '주식' ? 'ITEM_CD' : 'LEDGER_NO';
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" disabled={!product} onClick={() => setOpen((o) => !o)} style={{
        width: '100%', display: 'flex', alignItems: 'center', gap: 8, cursor: product ? 'pointer' : 'not-allowed', opacity: product ? 1 : .55,
        background: T.white, border: '1px solid ' + (open ? T.blue : T.c250), borderRadius: 8, padding: '9px 11px',
        boxShadow: open ? '0 0 0 3px ' + T.blueSurface : 'none', transition: 'border-color .12s, box-shadow .12s' }}>
        <Icon name="search" size={15} color={T.c400} />
        <span style={{ flex: 1, textAlign: 'left', minWidth: 0 }}>
          {sel ?
            <span style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
              <span style={{ fontFamily: T.font, fontWeight: 700, fontSize: 13, color: T.c900, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sel.name}</span>
              <span className="mono" style={{ fontFamily: T.mono, fontSize: 10.5, color: T.c400, whiteSpace: 'nowrap' }}>{sel.ledger}</span>
            </span> :
            <span style={{ fontFamily: T.font, fontWeight: 600, fontSize: 13, color: T.c400 }}>{product ? `종목명 · ${codeLabel} 검색` : '상품 구분을 먼저 선택하세요'}</span>}
        </span>
        <Icon name="chevDown" size={15} color={T.c400} stroke={2.2} style={{ transition: 'transform .15s', transform: open ? 'rotate(180deg)' : 'none' }} />
      </button>
      {open &&
        <div style={{ position: 'absolute', zIndex: 40, top: 'calc(100% + 5px)', left: 0, right: 0, background: T.white,
          border: '1px solid ' + T.c200, borderRadius: 10, boxShadow: R_POP, overflow: 'hidden' }}>
          <div style={{ padding: 8, borderBottom: '1px solid ' + T.line }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: T.c100, border: '1px solid ' + T.line, borderRadius: 7, padding: '7px 10px' }}>
              <Icon name="search" size={14} color={T.c400} />
              <input autoFocus value={q} onChange={(e) => setQ(e.target.value)} placeholder={`종목명 또는 ${codeLabel}`}
                style={{ border: 0, outline: 'none', flex: 1, minWidth: 0, fontFamily: T.font, fontWeight: 500, fontSize: 12.5, color: T.c800, background: 'transparent' }} />
            </div>
          </div>
          <div style={{ maxHeight: 256, overflowY: 'auto', padding: 5 }}>
            {filtered.map((i) => {
              const on = i.code === value;
              return (
                <button key={i.code} type="button" onClick={() => { onChange(i.code); setOpen(false); setQ(''); }} style={{
                  width: '100%', display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', textAlign: 'left',
                  background: on ? T.blueSurface : 'transparent', border: 0, borderRadius: 7, padding: '8px 9px' }}
                  onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = T.c100; }}
                  onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = 'transparent'; }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 12.5, color: on ? T.blue600 : T.c900, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{i.name}</div>
                    <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 11, color: T.c400, marginTop: 1 }}>{i.sub}</div>
                  </div>
                  <span className="mono" style={{ fontFamily: T.mono, fontSize: 10, color: on ? T.blue : T.c400, whiteSpace: 'nowrap' }}>{i.ledger}</span>
                </button>);
            })}
            {filtered.length === 0 &&
              <div style={{ padding: '24px', textAlign: 'center', fontFamily: T.font, fontWeight: 500, fontSize: 12.5, color: T.c400 }}>검색 결과가 없어요.</div>}
          </div>
        </div>}
    </div>);
}

function DateField({ value, onChange }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, background: T.white, border: '1px solid ' + T.c250, borderRadius: 8, padding: '7px 11px' }}>
      <Icon name="calendar" size={15} color={T.c400} />
      <input type="date" value={value} onChange={(e) => onChange(e.target.value)}
        style={{ border: 0, outline: 'none', flex: 1, fontFamily: T.font, fontWeight: 600, fontSize: 13, color: T.c800, background: 'transparent' }} />
    </div>);
}

// ---------------------------------------------------------------- meta
const REPORT_TYPES = [
  { v: '내부 기록용', l: '내부 기록용', sub: '리스크관리 내부 보관 문서', kind: 'neutral' },
  { v: '감사 대응',  l: '감사 대응',  sub: '내부·외부 감사 제출 자료', kind: 'blue' },
  { v: '감독원 보고', l: '감독원 보고', sub: '금융감독원 제출 보고서',    kind: 'warn' }];
const TYPE_KIND = { '내부 기록용': 'neutral', '감사 대응': 'blue', '감독원 보고': 'warn' };

function fmtEok(v) { return v == null ? '—' : v.toLocaleString('ko-KR') + '억원'; }
function ddayOf(d) { if (!d) return null; const today = new Date(); return Math.round((new Date(d) - today) / 86400000); }

// ---------------------------------------------------------------- report text generator
function buildReport(meta) {
  const { type, baseDate, product, item, purpose } = meta;
  const today = new Date().toISOString().slice(0, 10);
  const bd = baseDate.replace(/-/g, '.');
  const statusNarr = {
    '위험': '대상 종목의 측정값이 위험 기준을 상회하여 **위험** 등급으로 분류되었습니다. 즉시 한도 점검과 보유 축소·헤지 검토가 필요한 상태입니다.',
    '주의': '유보 기준의 80%를 초과하여 **주의** 등급으로 분류되었습니다. 일별 모니터링을 강화하고, 추가 악화 시 보고 체계를 가동합니다.',
    '유보': '산출 기준 미충족으로 **유보** 처리되었습니다. 평가 데이터 보완 후 재산출이 필요합니다.',
    '정상': '측정 결과 위험 기준 이내로 **정상** 등급입니다. 현행 정기 모니터링 주기를 유지합니다.' };
  const statusReco = {
    '위험': ['보유 포지션 축소 또는 헤지 전략 검토', '리스크관리위원회 보고 대상으로 상정', '일일 한도 소진율 모니터링으로 전환'],
    '주의': ['모니터링 주기를 일별로 전환', '한도 소진율 및 전일대비 변동 추적', '악화 시 Maker-Checker 승인 절차 가동'],
    '유보': ['평가 기초데이터 보완 및 정합성 점검', '재산출 일정 등록 후 등급 재판정'],
    '정상': ['현 모니터링 체계 유지', '다음 정기 점검 시 재평가'] };
  const typeIntro = {
    '내부 기록용': '본 보고서는 리스크관리 내부 기록 보관을 목적으로 작성되었습니다.',
    '감사 대응':   '본 보고서는 내부·외부 감사 대응 자료로 제출하기 위해 작성되었습니다.',
    '감독원 보고': '본 보고서는 금융감독원 제출용 보고 자료로 작성되었습니다.' };

  const L = [];
  L.push(`# ${item.name} 시장리스크 점검 보고서`);
  L.push('');
  L.push('## 1. 보고 개요');
  L.push(`${typeIntro[type]} ${bd} 종가를 기준으로 ${item.name}에 대한 시장리스크 측정 결과를 정리하였습니다.`);
  if (purpose && purpose.trim()) { L.push(''); L.push(purpose.trim()); }
  L.push('');
  L.push('## 2. 대상 종목 정보');
  if (product === '채권') {
    const dd = ddayOf(item.maturity);
    L.push(`• 종목명: ${item.name}`);
    L.push(`• ISIN / 원장번호: ${item.isin} / ${item.ledger}`);
    L.push(`• 상품 구분: 채권 · ${item.cat}${item.grade ? ` (${item.grade})` : ''}`);
    L.push(`• 통화: ${item.ccy}`);
    L.push(`• 만기일: ${item.maturity ? item.maturity.replace(/-/g, '.') : '—'}${dd != null ? (dd >= 0 ? ` (D-${dd})` : ' (만기 경과)') : ''}`);
    L.push(`• 보유금액: ${fmtEok(item.hold)}`);
  } else {
    L.push(`• 종목명: ${item.name}`);
    L.push(`• 종목코드(ITEM_CD): ${item.ticker}`);
    L.push(`• 상품 구분: 주식 · ${item.sector}`);
    L.push(`• 평가금액: ${fmtEok(item.eval)}`);
  }
  L.push('');
  L.push('## 3. 리스크 측정 결과');
  if (product === '채권') {
    const cvar = item.pending ? '산출 보류' : (item.cvar * 100).toFixed(2) + '%';
    const dod  = item.pending ? '—' : (item.dod >= 0 ? '+' : '') + (item.dod * 100).toFixed(2) + '%p';
    L.push(`• CVaR (99%, 10영업일): ${cvar}`);
    L.push(`• 전일대비: ${dod}`);
    L.push(`• 위험금액: ${fmtEok(item.risk)}`);
  } else {
    L.push(`• 변동성(연환산): ${item.risk.toFixed(1)}%`);
    L.push(`• 평가손익률: ${(item.ret >= 0 ? '+' : '') + item.ret.toFixed(1)}%`);
    L.push(`• 전일대비: ${(item.dod >= 0 ? '+' : '') + item.dod.toFixed(1)}%p`);
    L.push(`• 위험금액: ${fmtEok(item.riskAmt)}`);
  }
  L.push(`• 리스크 구분: ${item.status}`);
  if (item.note) L.push(`• 비고: ${item.note}`);
  L.push('');
  L.push('## 4. 평가 의견');
  L.push(statusNarr[item.status] || statusNarr['정상']);
  L.push('');
  L.push('## 5. 조치 및 권고');
  (statusReco[item.status] || statusReco['정상']).forEach((r) => L.push('• ' + r));
  L.push('');
  L.push('---');
  L.push(`작성: 리스크총괄부 · 작성일 ${today.replace(/-/g, '.')} · 분류: ${type}`);
  L.push('본 문서는 내부 리스크관리 목적의 자동 생성 초안이며, 담당자 검토 후 확정됩니다.');
  return L.join('\n');
}

// resolve form item -> data object
function resolveItem(product, code) {
  if (product === '주식') return STOCKS.find((s) => s.ticker === code);
  return BONDS.find((b) => b.isin === code);
}

// ---------------------------------------------------------------- document renderer
function renderDoc(text) {
  const lines = text.split('\n');
  const out = [];
  let bullets = null;
  const flush = () => {
    if (bullets) {
      out.push(<ul key={'ul' + out.length} style={{ margin: '4px 0 14px', padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 7 }}>{bullets}</ul>);
      bullets = null;
    }
  };
  const inline = (s) => {
    const parts = s.split(/(\*\*[^*]+\*\*)/g);
    return parts.map((p, i) => p.startsWith('**') && p.endsWith('**') ?
      <strong key={i} style={{ fontWeight: 700, color: T.c900 }}>{p.slice(2, -2)}</strong> : p);
  };
  lines.forEach((raw, i) => {
    const line = raw;
    if (line.startsWith('# ')) {
      flush();
      out.push(<h1 key={i} style={{ fontFamily: T.font, fontWeight: 800, fontSize: 22, color: T.c900, letterSpacing: '-.02em', margin: '0 0 18px', lineHeight: 1.3 }}>{line.slice(2)}</h1>);
    } else if (line.startsWith('## ')) {
      flush();
      out.push(<h2 key={i} style={{ fontFamily: T.font, fontWeight: 700, fontSize: 15, color: T.blue600, letterSpacing: '-.01em', margin: '22px 0 9px', paddingBottom: 7, borderBottom: '1px solid ' + T.line }}>{line.slice(3)}</h2>);
    } else if (line.startsWith('• ') || line.startsWith('- ')) {
      const content = line.slice(2);
      const m = content.match(/^([^:]+):\s*(.+)$/);
      (bullets = bullets || []).push(
        <li key={i} style={{ display: 'flex', gap: 10, fontFamily: T.font, fontSize: 13.5, lineHeight: 1.6, color: T.c700 }}>
          <span style={{ color: T.c300, marginTop: 2, flex: 'none' }}>•</span>
          {m ?
            <span><span style={{ color: T.c500, fontWeight: 600 }}>{m[1]}</span><span style={{ color: T.c300, margin: '0 6px' }}>·</span><span style={{ color: T.c900, fontWeight: 600 }}>{m[2]}</span></span> :
            <span>{inline(content)}</span>}
        </li>);
    } else if (line.trim() === '---') {
      flush();
      out.push(<hr key={i} style={{ border: 0, borderTop: '1px solid ' + T.line, margin: '24px 0 14px' }} />);
    } else if (line.trim() === '') {
      flush();
    } else {
      flush();
      out.push(<p key={i} style={{ fontFamily: T.font, fontWeight: 500, fontSize: 13.5, lineHeight: 1.75, color: T.c700, margin: '0 0 10px' }}>{inline(line)}</p>);
    }
  });
  flush();
  return out;
}

// ---------------------------------------------------------------- persistence + seeds
const RKEY = 'ibk-reports-v1';

function seedReports() {
  const today = new Date().toISOString().slice(0, 10);
  const mk = (product, code, type, baseDate, purpose, daysAgo) => {
    const item = resolveItem(product, code);
    if (!item) return null;
    const meta = { type, baseDate, product, item, purpose };
    const created = new Date(Date.now() - daysAgo * 86400000).toISOString();
    return { id: 'r' + Math.random().toString(36).slice(2, 9), title: item.name, type, baseDate, product, code,
      itemName: item.name, ledger: product === '주식' ? item.ticker : item.ledger, status: item.status,
      purpose, body: buildReport(meta), createdAt: created, updatedAt: created };
  };
  const b1 = BONDS[0];
  const b2 = BONDS.length > 3 ? BONDS[3] : BONDS[0];
  const s1 = STOCKS.find((s) => s.status === '위험') || STOCKS[0];
  return [
    mk('채권', b1.isin, '감독원 보고', today, '매도 권장 신호가 발생한 위험 등급 채권에 대한 감독원 보고 자료입니다.', 0),
    mk('주식', s1.ticker, '감사 대응', today, '분기 내부감사 대비 위험 등급 주식의 리스크 산출 근거를 정리합니다.', 1),
    mk('채권', b2.isin, '내부 기록용', today, '월말 정기 점검 기록.', 5),
  ].filter(Boolean);
}

function loadReports() {
  try { const raw = localStorage.getItem(RKEY); if (raw) { const a = JSON.parse(raw); if (Array.isArray(a) && a.length > 0) return a; } } catch (e) {}
  const s = seedReports();
  try { localStorage.setItem(RKEY, JSON.stringify(s)); } catch (e) {}
  return s;
}
function saveReports(list) { try { localStorage.setItem(RKEY, JSON.stringify(list)); } catch (e) {} }

function relTime(iso) {
  const d = Date.now() - Date.parse(iso);
  const day = Math.floor(d / 86400000);
  if (day <= 0) return '오늘';
  if (day === 1) return '어제';
  if (day < 7) return day + '일 전';
  return new Date(iso).toLocaleDateString('ko-KR', { month: 'numeric', day: 'numeric' });
}

// ---------------------------------------------------------------- left: list
function ReportList({ reports, activeId, onSelect, onNew }) {
  return (
    <Card style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      <div style={{ padding: '15px 16px 13px', borderBottom: '1px solid ' + T.line, flex: 'none' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: 12 }}>
          <div style={{ width: 30, height: 30, borderRadius: 8, background: T.c100, display: 'flex', alignItems: 'center', justifyContent: 'center', color: T.c600 }}>
            <Icon name="fileText" size={17} />
          </div>
          <div style={{ textAlign: 'left' }}>
            <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 14.5, color: T.c900 }}>보고서</div>
            <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 11.5, color: T.c500 }}>생성된 보고서 {reports.length}건</div>
          </div>
        </div>
        <Btn variant="primary" icon="plus" size="md" onClick={onNew} style={{ width: '100%' }}>새 보고서 생성</Btn>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: 8 }}>
        {reports.length === 0 &&
          <div style={{ padding: '34px 16px', textAlign: 'center', fontFamily: T.font, fontWeight: 500, fontSize: 12.5, color: T.c400 }}>아직 생성된 보고서가 없어요.</div>}
        {reports.map((r) => {
          const on = r.id === activeId;
          return (
            <button key={r.id} onClick={() => onSelect(r.id)} style={{
              width: '100%', display: 'block', textAlign: 'left', cursor: 'pointer', marginBottom: 5,
              background: on ? T.blueSurface : 'transparent', border: '1px solid ' + (on ? T.blueLine : 'transparent'),
              borderRadius: 9, padding: '10px 11px', transition: 'background .1s' }}
              onMouseEnter={(e) => { if (!on) e.currentTarget.style.background = T.c100; }}
              onMouseLeave={(e) => { if (!on) e.currentTarget.style.background = 'transparent'; }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6 }}>
                <Pill kind={TYPE_KIND[r.type] || 'neutral'} small>{r.type}</Pill>
                <span style={{ marginLeft: 'auto', fontFamily: T.font, fontWeight: 500, fontSize: 10.5, color: T.c400 }}>{relTime(r.updatedAt)}</span>
              </div>
              <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 13.5, color: on ? T.blue600 : T.c900, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.title}</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginTop: 5 }}>
                <span style={{ fontFamily: T.font, fontWeight: 600, fontSize: 11, color: T.c500 }}>{r.product}</span>
                <span style={{ width: 3, height: 3, borderRadius: 999, background: T.c300 }} />
                <span className="mono" style={{ fontFamily: T.mono, fontSize: 10, color: T.c400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.ledger}</span>
                <span style={{ marginLeft: 'auto', flex: 'none' }}><StatusPill status={r.status} small /></span>
              </div>
            </button>);
        })}
      </div>
    </Card>);
}

// ---------------------------------------------------------------- left: new-report form
function ReportForm({ onCancel, onGenerate, generating }) {
  const [type, setType] = React.useState('');
  const [baseDate, setBaseDate] = React.useState(new Date().toISOString().slice(0, 10));
  const [product, setProduct] = React.useState('');
  const [code, setCode] = React.useState('');
  const [purpose, setPurpose] = React.useState('');
  const ready = type && baseDate && product && code;

  const setProd = (p) => { setProduct(p); setCode(''); };
  return (
    <Card style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      <div style={{ padding: '14px 16px', borderBottom: '1px solid ' + T.line, flex: 'none', display: 'flex', alignItems: 'center', gap: 9 }}>
        <button onClick={onCancel} style={{ border: '1px solid ' + T.c250, background: T.white, borderRadius: 7, padding: 6, cursor: 'pointer', color: T.c600, display: 'flex' }}
          onMouseEnter={(e) => e.currentTarget.style.background = T.c100} onMouseLeave={(e) => e.currentTarget.style.background = T.white}>
          <Icon name="chevLeft" size={16} stroke={2.2} />
        </button>
        <div style={{ textAlign: 'left' }}>
          <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 14.5, color: T.c900 }}>새 보고서 생성</div>
          <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 11.5, color: T.c500 }}>설정을 입력하고 생성하세요</div>
        </div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: '18px 16px', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <Field label="보고서 종류" req>
          <RSelect value={type} onChange={setType} options={REPORT_TYPES} placeholder="종류 선택" icon="fileText" />
        </Field>
        <Field label="작성 기준일" req hint="종가 기준">
          <DateField value={baseDate} onChange={setBaseDate} />
        </Field>
        <Field label="상품 구분" req>
          <RSelect value={product} onChange={setProd} placeholder="상품 선택" icon="layers"
            options={[{ v: '주식', l: '주식', sub: '국내 보유 주식' }, { v: '채권', l: '채권', sub: '국내·해외 채권' }]} />
        </Field>
        <Field label="원장번호" req hint={product === '주식' ? 'ITEM_CD' : product === '채권' ? 'LEDGER_NO' : 'ITEM_CD · LEDGER_NO'}>
          <LedgerPicker product={product} value={code} onChange={setCode} />
        </Field>
        <Field label="보고서 목적 및 내용">
          <textarea value={purpose} onChange={(e) => setPurpose(e.target.value)} rows={4}
            placeholder="보고서의 작성 목적과 포함할 내용을 입력하세요."
            style={{ width: '100%', resize: 'vertical', minHeight: 92, border: '1px solid ' + T.c250, borderRadius: 8, padding: '10px 11px',
              fontFamily: T.font, fontWeight: 500, fontSize: 13, lineHeight: 1.6, color: T.c800, outline: 'none', background: T.white, boxSizing: 'border-box' }}
            onFocus={(e) => { e.target.style.borderColor = T.blue; e.target.style.boxShadow = '0 0 0 3px ' + T.blueSurface; }}
            onBlur={(e)  => { e.target.style.borderColor = T.c250; e.target.style.boxShadow = 'none'; }} />
        </Field>
      </div>
      <div style={{ padding: '13px 16px', borderTop: '1px solid ' + T.line, flex: 'none', display: 'flex', gap: 9 }}>
        <Btn variant="secondary" size="md" onClick={onCancel} style={{ flex: 'none' }}>취소</Btn>
        <Btn variant="primary" size="md" icon={generating ? null : 'sparkles'} disabled={!ready || generating}
          onClick={() => onGenerate({ type, baseDate, product, code, purpose })} style={{ flex: 1 }}>
          {generating ? <RSpinner label="생성 중…" /> : '생성하기'}
        </Btn>
      </div>
    </Card>);
}

function RSpinner({ label }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
      <span style={{ width: 14, height: 14, border: '2px solid rgba(255,255,255,.45)', borderTopColor: '#fff', borderRadius: 999, animation: 'rspin .7s linear infinite', display: 'inline-block' }} />
      {label}
    </span>);
}

// ---------------------------------------------------------------- right: viewer / editor
function ReportViewer({ report, editing, draft, onDraft, onToggleEdit, onSave, onRefresh, onDelete, generating, dirty }) {
  if (generating) {
    return (
      <Card style={{ height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16 }}>
        <span style={{ width: 30, height: 30, border: '3px solid ' + T.blueSurface, borderTopColor: T.blue, borderRadius: 999, animation: 'rspin .7s linear infinite' }} />
        <div style={{ textAlign: 'center' }}>
          <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 14, color: T.c800 }}>보고서를 생성하고 있어요</div>
          <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 12, color: T.c500, marginTop: 4 }}>리스크 측정 결과를 분석하는 중입니다…</div>
        </div>
      </Card>);
  }
  if (!report) {
    return (
      <Card style={{ height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 14 }}>
        <div style={{ width: 56, height: 56, borderRadius: 16, background: T.c100, display: 'flex', alignItems: 'center', justifyContent: 'center', color: T.c300 }}>
          <Icon name="fileText" size={28} />
        </div>
        <div style={{ textAlign: 'center', maxWidth: 280 }}>
          <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 15, color: T.c700 }}>보고서를 선택하세요</div>
          <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 12.5, color: T.c500, marginTop: 6, lineHeight: 1.6 }}>
            왼쪽에서 기존 보고서를 선택하거나, <b style={{ color: T.blue }}>새 보고서 생성</b>으로 새 문서를 만들 수 있어요.
          </div>
        </div>
      </Card>);
  }
  return (
    <Card style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
      {/* toolbar */}
      <div style={{ padding: '12px 16px', borderBottom: '1px solid ' + T.line, flex: 'none', display: 'flex', alignItems: 'center', gap: 12 }}>
        <div style={{ minWidth: 0, textAlign: 'left' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontFamily: T.font, fontWeight: 700, fontSize: 15, color: T.c900, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{report.title}</span>
            <Pill kind={TYPE_KIND[report.type] || 'neutral'} small>{report.type}</Pill>
            {editing && <Pill kind="info" small>편집 중</Pill>}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4, fontFamily: T.font, fontWeight: 500, fontSize: 11.5, color: T.c500 }}>
            <span>{report.product}</span>
            <span style={{ width: 3, height: 3, borderRadius: 999, background: T.c300 }} />
            <span className="mono" style={{ fontFamily: T.mono, fontSize: 10.5, color: T.c400 }}>{report.ledger}</span>
            <span style={{ width: 3, height: 3, borderRadius: 999, background: T.c300 }} />
            <span>기준일 {report.baseDate.replace(/-/g, '.')}</span>
          </div>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 7, flex: 'none' }}>
          <Btn variant="secondary" size="sm" icon="refresh" onClick={onRefresh}>새로고침</Btn>
          <Btn variant={editing ? 'dark' : 'secondary'} size="sm" icon="edit" onClick={onToggleEdit}>{editing ? '편집 종료' : '편집'}</Btn>
          <Btn variant="primary" size="sm" icon="check" onClick={onSave} disabled={editing ? !dirty : false}>저장</Btn>
          <div style={{ width: 1, height: 22, background: T.line }} />
          <Btn variant="secondary" size="sm" icon="x" onClick={onDelete} style={{ color: T.alert, borderColor: T.alertL }}>삭제</Btn>
        </div>
      </div>
      {/* body */}
      <div style={{ flex: 1, overflowY: 'auto', background: T.c50 }}>
        <div style={{ maxWidth: 720, margin: '0 auto', padding: '26px 22px 40px' }}>
          {editing ?
            <textarea value={draft} onChange={(e) => onDraft(e.target.value)} spellCheck={false}
              style={{ width: '100%', minHeight: 'calc(100vh - 360px)', border: '1px solid ' + T.c250, borderRadius: 10, padding: '18px 20px',
                fontFamily: T.font, fontWeight: 500, fontSize: 13.5, lineHeight: 1.75, color: T.c800, outline: 'none', background: T.white,
                resize: 'vertical', boxSizing: 'border-box', boxShadow: '0 1px 2px rgba(15,23,34,.04)' }}
              onFocus={(e) => e.target.style.borderColor = T.blue} onBlur={(e) => e.target.style.borderColor = T.c250} /> :
            <div style={{ background: T.white, border: '1px solid ' + T.line, borderRadius: 12, padding: '34px 36px', boxShadow: '0 1px 2px rgba(15,23,34,.04)', textAlign: 'left' }}>
              {renderDoc(report.body)}
            </div>}
        </div>
      </div>
      <div style={{ padding: '9px 16px', borderTop: '1px solid ' + T.line, flex: 'none', display: 'flex', alignItems: 'center', gap: 10, fontFamily: T.font, fontWeight: 500, fontSize: 11, color: T.c400 }}>
        <Icon name="clock" size={13} color={T.c400} />
        <span>최종 수정 {new Date(report.updatedAt).toLocaleString('ko-KR', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })}</span>
        {editing && dirty && <span style={{ marginLeft: 'auto', color: T.warn, fontWeight: 600 }}>저장되지 않은 변경사항이 있어요</span>}
      </div>
    </Card>);
}

// ---------------------------------------------------------------- confirm modal
function RConfirm({ open, title, body, confirmLabel, danger, onConfirm, onCancel }) {
  if (!open) return null;
  return (
    <div onClick={onCancel} style={{ position: 'fixed', inset: 0, zIndex: 200, background: 'rgba(15,23,34,.42)', display: 'flex', alignItems: 'center', justifyContent: 'center', animation: 'rfade .12s ease-out' }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 380, background: T.white, borderRadius: 16, boxShadow: R_POP, padding: '24px 24px 18px', textAlign: 'left' }}>
        <div style={{ fontFamily: T.font, fontWeight: 700, fontSize: 16, color: T.c900 }}>{title}</div>
        <div style={{ fontFamily: T.font, fontWeight: 500, fontSize: 13, color: T.c600, marginTop: 8, lineHeight: 1.6 }}>{body}</div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 9, marginTop: 22 }}>
          <Btn variant="secondary" size="md" onClick={onCancel}>취소</Btn>
          <Btn variant={danger ? 'danger' : 'primary'} size="md" onClick={onConfirm}>{confirmLabel}</Btn>
        </div>
      </div>
    </div>);
}

// ---------------------------------------------------------------- main
function Report() {
  const [reports, setReports] = React.useState(loadReports);
  const [activeId, setActiveId] = React.useState(() => { const r = loadReports(); return r[0] ? r[0].id : null; });
  const [mode, setMode] = React.useState('list'); // list | form
  const [generating, setGenerating] = React.useState(false);
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft] = React.useState('');
  const [confirm, setConfirm] = React.useState(null);

  const active = reports.find((r) => r.id === activeId) || null;
  const dirty = editing && active && draft !== active.body;

  const persist = (list) => { setReports(list); saveReports(list); };

  const selectReport = (id) => {
    if (dirty) { setConfirm({ kind: 'leave', next: id }); return; }
    setActiveId(id); setEditing(false); setMode('list');
  };

  const startNew = () => {
    if (dirty) { setConfirm({ kind: 'leaveNew' }); return; }
    setEditing(false); setMode('form');
  };

  const doGenerate = (form) => {
    setGenerating(true);
    setMode('list');
    setActiveId(null);
    setTimeout(() => {
      const item = resolveItem(form.product, form.code);
      const meta = { type: form.type, baseDate: form.baseDate, product: form.product, item, purpose: form.purpose };
      const now = new Date().toISOString();
      const r = { id: 'r' + Math.random().toString(36).slice(2, 9), title: item.name, type: form.type,
        baseDate: form.baseDate, product: form.product, code: form.code,
        itemName: item.name, ledger: form.product === '주식' ? item.ticker : item.ledger, status: item.status,
        purpose: form.purpose, body: buildReport(meta), createdAt: now, updatedAt: now };
      const list = [r, ...reports];
      persist(list);
      setActiveId(r.id);
      setGenerating(false);
      setEditing(false);
    }, 950);
  };

  const toggleEdit = () => {
    if (editing) {
      if (dirty) { setConfirm({ kind: 'discardEdit' }); return; }
      setEditing(false);
    } else { setDraft(active.body); setEditing(true); }
  };

  const save = () => {
    if (!active) return;
    const body = editing ? draft : active.body;
    const list = reports.map((r) => r.id === active.id ? { ...r, body, updatedAt: new Date().toISOString() } : r);
    persist(list);
    setEditing(false);
    showToast('저장되었습니다');
  };

  const refresh = () => {
    if (!active) return;
    if (dirty) { setConfirm({ kind: 'refresh' }); return; }
    runRefresh();
  };
  const runRefresh = () => {
    setGenerating(true);
    setTimeout(() => {
      const item = resolveItem(active.product, active.code);
      const meta = { type: active.type, baseDate: active.baseDate, product: active.product, item, purpose: active.purpose };
      const body = buildReport(meta);
      const list = reports.map((r) => r.id === active.id ? { ...r, body, updatedAt: new Date().toISOString() } : r);
      persist(list);
      setEditing(false);
      setGenerating(false);
      showToast('최신 데이터로 다시 생성했어요');
    }, 950);
  };

  const del = () => setConfirm({ kind: 'delete' });
  const runDelete = () => {
    const idx = reports.findIndex((r) => r.id === active.id);
    const list = reports.filter((r) => r.id !== active.id);
    persist(list);
    const nextSel = list[idx] || list[idx - 1] || null;
    setActiveId(nextSel ? nextSel.id : null);
    setEditing(false);
  };

  const [toast, setToastRaw] = React.useState(null);
  const showToast = (msg) => { setToastRaw(msg); setTimeout(() => setToastRaw(null), 1900); };

  const resolveConfirm = () => {
    const c = confirm; setConfirm(null);
    if (!c) return;
    if (c.kind === 'delete')       runDelete();
    else if (c.kind === 'refresh') { setEditing(false); runRefresh(); }
    else if (c.kind === 'discardEdit') setEditing(false);
    else if (c.kind === 'leave')   { setActiveId(c.next); setEditing(false); setMode('list'); }
    else if (c.kind === 'leaveNew') { setEditing(false); setMode('form'); }
  };
  const CONF = {
    delete:      { title: '보고서를 삭제할까요?',   body: '삭제하면 되돌릴 수 없어요. 선택한 보고서가 영구적으로 제거됩니다.', confirmLabel: '삭제', danger: true },
    refresh:     { title: '다시 생성할까요?',       body: '편집한 내용이 사라지고 최신 데이터로 보고서를 다시 생성해요.', confirmLabel: '다시 생성' },
    discardEdit: { title: '편집을 종료할까요?',     body: '저장하지 않은 변경사항이 사라져요.', confirmLabel: '종료' },
    leave:       { title: '다른 보고서로 이동할까요?', body: '저장하지 않은 변경사항이 사라져요.', confirmLabel: '이동' },
    leaveNew:    { title: '새 보고서를 만들까요?',  body: '저장하지 않은 변경사항이 사라져요.',  confirmLabel: '계속' } };
  const cc = confirm ? CONF[confirm.kind] : null;

  return (
    <div style={{ display: 'flex', gap: 16, height: 'calc(100vh - 232px)', minHeight: 580, alignItems: 'stretch' }}>
      <div style={{ width: 348, flex: 'none', minHeight: 0 }}>
        {mode === 'form' ?
          <ReportForm generating={generating} onCancel={() => setMode('list')} onGenerate={doGenerate} /> :
          <ReportList reports={reports} activeId={activeId} onSelect={selectReport} onNew={startNew} />}
      </div>
      <div style={{ flex: 1, minWidth: 0, minHeight: 0 }}>
        <ReportViewer report={active} editing={editing} draft={draft} onDraft={setDraft}
          generating={generating} dirty={dirty}
          onToggleEdit={toggleEdit} onSave={save} onRefresh={refresh} onDelete={del} />
      </div>

      <RConfirm open={!!confirm} title={cc && cc.title} body={cc && cc.body} confirmLabel={cc && cc.confirmLabel} danger={cc && cc.danger}
        onConfirm={resolveConfirm} onCancel={() => setConfirm(null)} />

      {toast &&
        <div style={{ position: 'fixed', bottom: 28, left: '50%', transform: 'translateX(-50%)', zIndex: 250,
          background: T.c900, color: '#fff', borderRadius: 10, padding: '11px 18px', boxShadow: R_POP,
          fontFamily: T.font, fontWeight: 600, fontSize: 13, display: 'flex', alignItems: 'center', gap: 8, animation: 'rtoast .16s ease-out' }}>
          <Icon name="checkCircle" size={16} color={T.ok} /> {toast}
        </div>}
    </div>);
}
Object.assign(window, { Report });
