// 쿠폰 — 실서버 /admin/coupon
//
// 지금까지 어드민에 쿠폰 화면이 없어서, 쿠폰을 만들려면 DB 를 직접 건드려야 했습니다(점검에서 나온 빈칸).
// 손님 앱은 쿠폰을 실제로 씁니다.
//
// 발행은 **되돌리기 어려운 조작**입니다(발행하면 그 사람 지갑에 들어가고, 서버에 회수 경로가 없습니다).
// 그래서 발행 전에 **누구에게·얼마·언제까지**를 그대로 보여 주고 확인을 받습니다(리더 지시).
// 쿠폰 만들기·삭제도 확인을 거칩니다. 삭제는 soft delete(isDelete=Y)이고 이미 발행된 쿠폰은 남습니다.
//
// 서버가 막는 것(화면이 흉내 내지 않고 메시지를 그대로 보여 줍니다):
//   발행 기간 밖 · 삭제된 쿠폰 · 없는 사용자 · 이미 같은 쿠폰을 들고 있음(미사용 상태)

const COUPON_PAGE_SIZE = 20;

const COUPON_TYPE = { GENERAL: '일반 쿠폰', FRIEND_INVITE: '친구 초대 쿠폰' };
const DISCOUNT_TYPE = { AMOUNT: '금액 할인', PERCENT: '비율 할인' };
const COUPON_ISSUE_STATUS = {
  UNUSED:  { label: '미사용', tone: 'info' },
  USED:    { label: '사용',   tone: 'success' },
  EXPIRED: { label: '만료',   tone: 'neutral' },
  REVOKED: { label: '회수됨', tone: 'danger' },
};

// 할인 표기 — 금액이면 원, 비율이면 %.
const discountText = (c) => (c.discountType === 'PERCENT'
  ? `${fmtNum(c.discountValue)}%`
  : fmtKRW(c.discountValue));

const couponPeriodText = (c) => {
  const a = c.issueStartDate ? ymd(c.issueStartDate) : null;
  const b = c.issueEndDate ? ymd(c.issueEndDate) : null;
  return a && b ? `${a} ~ ${b}` : EMPTY_VALUE;
};

// 목록 응답이 **두 모양**으로 올 수 있습니다: 배열(지금)과 `{totalCount, list}`(backend 가 바꾸는 중).
// 배포 순서와 무관하게 깨지지 않도록 둘 다 읽습니다. 총건수를 주면 "총 N건"과 마지막 페이지를 정확히 쓰고,
// 안 주면 예전처럼 페이지 단위로만 셉니다(없는 총계를 지어내지 않습니다).
const couponRows = (data) => (Array.isArray(data) ? data : (data && data.list) || []);
const couponTotal = (data) => (Array.isArray(data) || !data ? null : (data.totalCount == null ? null : Number(data.totalCount)));

const Coupons = () => {
  const [tab, setTab] = React.useState('coupons');
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>쿠폰</h1>
          <div className="sub">쿠폰을 만들고 사용자에게 발행합니다 · 발행은 되돌릴 수 없습니다</div>
        </div>
      </div>
      <div className="table-wrap" style={{ marginBottom:14 }}>
        <Tabs items={[{ value:'coupons', label:'쿠폰' }, { value:'issues', label:'발행 내역' }]} value={tab} onChange={setTab}/>
      </div>
      {tab === 'coupons' ? <CouponList/> : <CouponIssueList/>}
    </div>
  );
};

// ── 쿠폰 목록 ────────────────────────────────────────────────────────────────
const CouponList = () => {
  const [keyword, setKeyword] = React.useState('');
  const [applied, setApplied] = React.useState('');
  const [includeDeleted, setIncludeDeleted] = React.useState(false);
  const [page, setPage] = React.useState(1);
  const [openId, setOpenId] = React.useState(null);
  const [creating, setCreating] = React.useState(false);
  const query = React.useMemo(() => {
    const q = { page, size: COUPON_PAGE_SIZE };
    if (applied.trim()) q.keyword = applied.trim();
    if (!includeDeleted) q.isDelete = 'N';
    return q;
  }, [applied, includeDeleted, page]);
  const list = useLive(() => api.live.coupons.list(query), [JSON.stringify(query)]);
  const rows = couponRows(list.data);
  const total = couponTotal(list.data);
  const lastPage = total == null ? null : Math.max(1, Math.ceil(total / COUPON_PAGE_SIZE));
  React.useEffect(() => { setPage(1); }, [applied, includeDeleted]);

  return (
    <>
      <div className="table-wrap" data-loading={list.loading ? 'true' : 'false'}>
        <div className="table-toolbar" style={{ flexWrap:'wrap' }}>
          <input className="input" style={{ flex:1, minWidth:180, maxWidth:300 }} placeholder="쿠폰 이름"
            value={keyword} onChange={(e) => setKeyword(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') setApplied(keyword); }}/>
          <button className="btn btn-primary btn-sm" onClick={() => setApplied(keyword)}><Icon name="search" size={14}/>검색</button>
          <button className="btn btn-ghost btn-sm" onClick={() => { setKeyword(''); setApplied(''); }}>초기화</button>
          <label style={{ display:'flex', alignItems:'center', gap:6, fontSize:13, color:'var(--text-2)' }}>
            <input type="checkbox" checked={includeDeleted} onChange={(e) => setIncludeDeleted(e.target.checked)}/>
            삭제된 쿠폰도 보기
          </label>
          <button className="btn btn-primary btn-sm" style={{ marginLeft:'auto' }} onClick={() => setCreating(true)}>
            <Icon name="plus" size={14}/>쿠폰 만들기
          </button>
        </div>
        <LiveBlock state={list} empty={rows.length === 0} emptyTitle="쿠폰이 없습니다">
          <table className="table" style={{ opacity: list.loading ? 0.45 : 1 }}>
            <thead><tr>
              <th>쿠폰명</th><th>종류</th><th>할인</th><th>최소 주문</th><th>사용 기한</th><th>발행 기간</th><th>상태</th>
            </tr></thead>
            <tbody>
              {rows.map((c) => (
                <tr key={c.couponId} onClick={() => setOpenId(c.couponId)} style={{ cursor:'pointer' }}>
                  <td style={{ fontWeight:600 }}>{c.name || EMPTY_VALUE}</td>
                  <td style={{ color:'var(--text-2)' }}>{COUPON_TYPE[c.couponType] || c.couponType}</td>
                  <td style={{ fontWeight:600 }}>{discountText(c)}</td>
                  <td>{c.minOrderAmount ? fmtKRW(c.minOrderAmount) : '없음'}</td>
                  <td>{c.validDay != null ? `받은 날부터 ${fmtNum(c.validDay)}일` : EMPTY_VALUE}</td>
                  <td style={{ fontSize:12.5, color:'var(--text-3)' }}>{couponPeriodText(c)}</td>
                  <td>{c.isDelete === 'Y'
                    ? <Badge tone="neutral">삭제됨</Badge>
                    : <Badge tone="success">사용 중</Badge>}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </LiveBlock>
        <div className="table-foot">
          <span style={{ fontSize:13, color:'var(--text-3)' }}>
            {total == null
              ? `${page}페이지 · ${rows.length}건 (서버가 총건수를 주지 않아 페이지 단위로 셉니다)`
              : `총 ${fmtNum(total)}건 · ${page}/${lastPage}페이지`}
          </span>
          <div style={{ display:'flex', gap:6 }}>
            <button className="btn btn-ghost btn-sm" disabled={page <= 1 || list.loading} onClick={() => setPage((p) => p - 1)}><Icon name="chevLeft" size={14}/>이전</button>
            <button className="btn btn-ghost btn-sm" disabled={list.loading || (lastPage == null ? rows.length < COUPON_PAGE_SIZE : page >= lastPage)} onClick={() => setPage((p) => p + 1)}>다음<Icon name="chevRight" size={14}/></button>
          </div>
        </div>
      </div>

      {creating && <CouponCreateDrawer onClose={() => setCreating(false)} onCreated={() => { setCreating(false); list.reload(); }}/>}
      {openId != null && <CouponDrawer couponId={openId} onClose={() => setOpenId(null)} onChanged={list.reload}/>}
    </>
  );
};

// ── 쿠폰 만들기 ──────────────────────────────────────────────────────────────
const couponBlank = () => ({
  couponType: 'GENERAL', name: '', discountType: 'AMOUNT',
  discountValue: '', minOrderAmount: '', validDay: '',
  issueStartDate: '', issueEndDate: '',
});

const CouponCreateDrawer = ({ onClose, onCreated }) => {
  const [f, setF] = React.useState(couponBlank);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  // 폼을 열자마자 경고부터 띄우지 않습니다(입점 신청 계정 발급 폼과 같은 규칙).
  const [touched, setTouched] = React.useState(false);
  const set = (k, v) => { setTouched(true); setF((x) => ({ ...x, [k]: v })); };
  const num = (v) => (v === '' || v == null ? null : Number(v));

  const problems = [];
  if (!f.name.trim()) problems.push('쿠폰 이름을 적어 주세요.');
  if (!(num(f.discountValue) > 0)) problems.push('할인 값은 0보다 커야 합니다.');
  if (f.discountType === 'PERCENT' && num(f.discountValue) > 100) problems.push('비율 할인은 100%를 넘을 수 없습니다.');
  if (!(num(f.validDay) > 0)) problems.push('사용 기한(일)은 1일 이상이어야 합니다.');
  if (!f.issueStartDate || !f.issueEndDate) problems.push('발행 기간을 정해 주세요.');
  if (f.issueStartDate && f.issueEndDate && f.issueStartDate > f.issueEndDate) problems.push('발행 시작일이 종료일보다 뒤입니다.');

  const submit = async () => {
    if (problems.length) return;
    if (!window.confirm(`쿠폰을 만들까요?\n\n${f.name.trim()}\n할인 ${f.discountType === 'PERCENT' ? `${num(f.discountValue)}%` : fmtKRW(num(f.discountValue))}`
      + `\n최소 주문 ${num(f.minOrderAmount) ? fmtKRW(num(f.minOrderAmount)) : '없음'}`
      + `\n받은 날부터 ${num(f.validDay)}일 사용\n발행 기간 ${f.issueStartDate} ~ ${f.issueEndDate}`)) return;
    setBusy(true); setError('');
    try {
      // 서버는 'yyyy-MM-dd HH:mm:ss' 를 받습니다. 시작은 그날 0시, 종료는 그날 끝까지로 보냅니다.
      await api.live.coupons.create({
        couponType: f.couponType,
        name: f.name.trim(),
        discountType: f.discountType,
        discountValue: num(f.discountValue),
        minOrderAmount: num(f.minOrderAmount) || 0,
        validDay: num(f.validDay),
        issueStartDate: `${f.issueStartDate} 00:00:00`,
        issueEndDate: `${f.issueEndDate} 23:59:59`,
      });
      onCreated();
    } catch (e) { setError(errorText(e)); }
    finally { setBusy(false); }
  };

  return (
    <Drawer open onClose={onClose} title="쿠폰 만들기"
      footer={<>
        <button className="btn btn-ghost" onClick={onClose} disabled={busy}>취소</button>
        <button className="btn btn-primary" onClick={submit} disabled={busy || problems.length > 0}>
          {busy ? '만드는 중…' : '쿠폰 만들기'}
        </button>
      </>}>
      {error && <div style={{ marginBottom:12 }}><Notice tone="danger">만들지 못했습니다: {error}</Notice></div>}
      <div style={{ display:'flex', flexDirection:'column', gap:14 }}>
        <CouponField label="쿠폰 이름">
          <input className="input" style={{ width:'100%' }} value={f.name} maxLength={100}
            onChange={(e) => set('name', e.target.value)} placeholder="사용자에게 보이는 이름입니다"/>
        </CouponField>
        <CouponField label="종류">
          <select className="select" value={f.couponType} onChange={(e) => set('couponType', e.target.value)}>
            {Object.keys(COUPON_TYPE).map((k) => <option key={k} value={k}>{COUPON_TYPE[k]}</option>)}
          </select>
        </CouponField>
        <CouponField label="할인">
          <div style={{ display:'flex', gap:8 }}>
            <select className="select" value={f.discountType} onChange={(e) => set('discountType', e.target.value)}>
              {Object.keys(DISCOUNT_TYPE).map((k) => <option key={k} value={k}>{DISCOUNT_TYPE[k]}</option>)}
            </select>
            <input className="input" type="number" min="1" style={{ flex:1 }} value={f.discountValue}
              onChange={(e) => set('discountValue', e.target.value)}
              placeholder={f.discountType === 'PERCENT' ? '예: 10 (%)' : '예: 3000 (원)'}/>
          </div>
        </CouponField>
        <CouponField label="최소 주문 금액 (선택)">
          <input className="input" type="number" min="0" style={{ width:'100%' }} value={f.minOrderAmount}
            onChange={(e) => set('minOrderAmount', e.target.value)} placeholder="비우면 제한 없음"/>
        </CouponField>
        <CouponField label="사용 기한">
          <div style={{ display:'flex', alignItems:'center', gap:8 }}>
            <span style={{ fontSize:13, color:'var(--text-3)' }}>받은 날부터</span>
            <input className="input" type="number" min="1" style={{ width:110 }} value={f.validDay}
              onChange={(e) => set('validDay', e.target.value)} placeholder="일"/>
            <span style={{ fontSize:13, color:'var(--text-3)' }}>일</span>
          </div>
        </CouponField>
        <CouponField label="발행 기간">
          <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
            <input className="input" type="date" value={f.issueStartDate} onChange={(e) => set('issueStartDate', e.target.value)}/>
            <span style={{ color:'var(--text-3)' }}>~</span>
            <input className="input" type="date" value={f.issueEndDate} onChange={(e) => set('issueEndDate', e.target.value)}/>
          </div>
          <div style={{ fontSize:12, color:'var(--text-3)', marginTop:6 }}>
            이 기간 안에서만 발행할 수 있습니다. 기간 밖이면 서버가 발행을 막습니다.
          </div>
        </CouponField>
        {touched && problems.length > 0 && (
          <Notice tone="danger">{problems.map((p, i) => <div key={i}>{p}</div>)}</Notice>
        )}
      </div>
    </Drawer>
  );
};

const CouponField = ({ label, children }) => (
  <div>
    <label style={{ display:'block', fontSize:13, fontWeight:600, marginBottom:6 }}>{label}</label>
    {children}
  </div>
);

// ── 쿠폰 상세 · 발행 ─────────────────────────────────────────────────────────
const CouponDrawer = ({ couponId, onClose, onChanged }) => {
  const detail = useLive(() => api.live.coupons.get(couponId), [couponId]);
  const issued = useLive(() => api.live.coupons.issuedUsers(couponId, { page: 1, size: 100 }), [couponId]);
  const c = detail.data || null;
  const [issuing, setIssuing] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const [notice, setNotice] = React.useState('');

  const remove = async () => {
    if (!window.confirm(`"${c.name}" 쿠폰을 삭제할까요?\n새로 발행할 수 없게 됩니다. 이미 발행된 쿠폰은 사용자 지갑에 그대로 남습니다.`)) return;
    setBusy(true); setError('');
    try { await api.live.coupons.remove(couponId); detail.reload(); onChanged(); }
    catch (e) { setError(errorText(e)); }
    finally { setBusy(false); }
  };

  const rows = couponRows(issued.data);
  return (
    <Drawer open onClose={onClose} title={c ? c.name : `쿠폰 ${couponId}`}
      footer={<>
        {c && c.isDelete !== 'Y' && !issuing && (
          <button className="btn btn-danger-ghost" style={{ marginRight:'auto' }} disabled={busy} onClick={remove}>쿠폰 삭제</button>
        )}
        {c && c.isDelete !== 'Y' && !issuing && (
          <button className="btn btn-primary" onClick={() => { setIssuing(true); setNotice(''); }}>사용자에게 발행</button>
        )}
        <button className="btn btn-ghost" onClick={onClose}>닫기</button>
      </>}>
      {error && <div style={{ marginBottom:12 }}><Notice tone="danger">처리하지 못했습니다: {error}</Notice></div>}
      {notice && <div style={{ marginBottom:12 }}><Notice>{notice}</Notice></div>}
      <LiveBlock state={detail} empty={!c} emptyTitle="쿠폰을 찾을 수 없습니다">
        {c && (
          <div>
            {c.isDelete === 'Y' && (
              <div style={{ marginBottom:12 }}><Notice tone="warn">삭제된 쿠폰입니다. 새로 발행할 수 없습니다.</Notice></div>
            )}
            <dl className="spec">
              <dt>쿠폰 번호</dt><dd className="t-mono">{c.couponId}</dd>
              <dt>종류</dt><dd>{COUPON_TYPE[c.couponType] || c.couponType}</dd>
              <dt>할인</dt><dd style={{ fontWeight:700 }}>{discountText(c)}</dd>
              <dt>최소 주문</dt><dd>{c.minOrderAmount ? fmtKRW(c.minOrderAmount) : '없음'}</dd>
              <dt>사용 기한</dt><dd>{c.validDay != null ? `받은 날부터 ${fmtNum(c.validDay)}일` : EMPTY_VALUE}</dd>
              <dt>발행 기간</dt><dd>{couponPeriodText(c)}</dd>
              <dt>만든 날</dt><dd>{c.regDate ? fmtDT(c.regDate) : EMPTY_VALUE}</dd>
            </dl>

            {issuing && (
              <CouponIssueForm coupon={c}
                onCancel={() => setIssuing(false)}
                onIssued={(who) => {
                  setIssuing(false);
                  setNotice(`${who} 님에게 발행했습니다.`);
                  issued.reload(); onChanged();
                }}/>
            )}

            <SectionTitle right={issued.data ? `${rows.length}명${rows.length >= 100 ? '+' : ''}` : ''}>발행받은 사용자</SectionTitle>
            <LiveBlock state={issued} empty={rows.length === 0} compact emptyTitle="아직 발행받은 사용자가 없습니다">
              <table className="table">
                <thead><tr><th>사용자 번호</th><th>닉네임</th><th>연락처</th></tr></thead>
                <tbody>
                  {rows.map((u) => (
                    <tr key={u.uid}>
                      <td className="t-mono">{u.uid}</td>
                      <td>{u.nickName || EMPTY_VALUE}</td>
                      <td className="t-mono">{maskPhone(u.phoneNumber)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </LiveBlock>
          </div>
        )}
      </LiveBlock>
    </Drawer>
  );
};

// 발행 — 사용자를 찾아 **누구에게·얼마·언제까지**를 보여 주고 확인을 받습니다.
const CouponIssueForm = ({ coupon, onCancel, onIssued }) => {
  const [field, setField] = React.useState('uid');
  const [keyword, setKeyword] = React.useState('');
  const [query, setQuery] = React.useState(null);
  const [picked, setPicked] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const found = useLive(() => (query ? api.live.users.search(query) : Promise.resolve(null)), [JSON.stringify(query)]);
  // 사용자 검색도 {totalCount, list} 로 바뀌었습니다(backend #217). 두 모양 다 읽습니다.
  const rows = listRows(found.data);

  const search = () => {
    const kw = keyword.trim();
    if (!kw) return;
    setPicked(null); setError('');
    setQuery(field === 'phoneNumber' ? { phoneNumber: kw.replace(/\D/g, '') } : { [field]: kw });
  };

  const issue = async () => {
    if (!picked) return;
    const until = `받은 날부터 ${fmtNum(coupon.validDay)}일`;
    if (!window.confirm(
      `이 사용자에게 쿠폰을 발행할까요?\n\n받는 사람: ${picked.nickName || '(닉네임 없음)'} · 사용자 ${picked.uid}\n`
      + `${picked.email || ''}\n\n쿠폰: ${coupon.name}\n할인: ${discountText(coupon)}\n사용 기한: ${until}\n\n`
      + `발행하면 되돌릴 수 없습니다.`)) return;
    setBusy(true); setError('');
    try {
      await api.live.coupons.issue(coupon.couponId, picked.uid);
      onIssued(picked.nickName || `사용자 ${picked.uid}`);
    } catch (e) { setError(errorText(e)); }
    finally { setBusy(false); }
  };

  return (
    <div style={{ margin:'12px 0 4px', padding:12, border:'1px solid var(--border)', borderRadius:8 }}>
      <div style={{ fontSize:13, fontWeight:700, marginBottom:8 }}>사용자에게 발행</div>
      {error && <div style={{ marginBottom:10 }}><Notice tone="danger">발행하지 못했습니다: {error}</Notice></div>}
      <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginBottom:10 }}>
        <select className="select" value={field} onChange={(e) => setField(e.target.value)} aria-label="검색 항목">
          <option value="uid">사용자 번호</option>
          <option value="email">이메일</option>
          <option value="nickName">닉네임</option>
          <option value="phoneNumber">연락처</option>
        </select>
        <input className="input" style={{ flex:1, minWidth:160 }} value={keyword}
          onChange={(e) => setKeyword(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') search(); }}
          placeholder="받을 사람을 찾습니다"/>
        <button className="btn btn-primary btn-sm" onClick={search} disabled={!keyword.trim()}>찾기</button>
        <button className="btn btn-ghost btn-sm" onClick={onCancel}>닫기</button>
      </div>

      {query && (
        <LiveBlock state={found} empty={rows.length === 0} compact emptyTitle="찾는 사용자가 없습니다">
          <div style={{ display:'flex', flexDirection:'column', gap:6, marginBottom:10 }}>
            {rows.slice(0, 10).map((u) => (
              <label key={u.uid} style={{
                display:'flex', alignItems:'center', gap:10, padding:'8px 10px', fontSize:13,
                border:`1px solid ${picked && picked.uid === u.uid ? 'var(--brand, #f5a623)' : 'var(--border)'}`, borderRadius:8, cursor:'pointer',
              }}>
                <input type="radio" name="coupon-user" checked={!!picked && picked.uid === u.uid} onChange={() => setPicked(u)}/>
                <span style={{ fontWeight:600 }}>{u.nickName || '(닉네임 없음)'}</span>
                <span className="t-mono" style={{ color:'var(--text-3)' }}>#{u.uid}</span>
                <span style={{ color:'var(--text-2)' }}>{maskEmail(u.email)}</span>
                <span style={{ marginLeft:'auto' }}><StatusBadge map={ACCOUNT_STATUS} value={u.status}/></span>
              </label>
            ))}
          </div>
        </LiveBlock>
      )}

      {picked && (
        // 발행 전 미리보기 — 누구에게·얼마·언제까지(리더 지시).
        <div style={{ padding:12, borderRadius:8, background:'var(--bg-2, #fafafa)', fontSize:13, lineHeight:1.8 }}>
          <div style={{ fontWeight:700, marginBottom:4 }}>이대로 발행합니다</div>
          <div>받는 사람 · <strong>{picked.nickName || '(닉네임 없음)'}</strong> <span className="t-mono" style={{ color:'var(--text-3)' }}>#{picked.uid}</span> {picked.email}</div>
          <div>쿠폰 · {coupon.name} ({COUPON_TYPE[coupon.couponType] || coupon.couponType})</div>
          <div>할인 · <strong>{discountText(coupon)}</strong>{coupon.minOrderAmount ? ` · ${fmtKRW(coupon.minOrderAmount)} 이상 주문` : ''}</div>
          <div>사용 기한 · 받은 날부터 {fmtNum(coupon.validDay)}일</div>
          <div style={{ color:'var(--text-3)', marginTop:6 }}>
            발행하면 곧바로 그 사람 지갑에 들어갑니다. <strong>쓰기 전이라면</strong> 발행 내역에서 회수할 수 있지만,
            <strong> 이미 사용한 쿠폰은 되돌릴 수 없습니다.</strong>
          </div>
          <button className="btn btn-primary btn-sm" style={{ marginTop:10 }} onClick={issue} disabled={busy}>
            {busy ? '발행 중…' : '이 사용자에게 발행'}
          </button>
        </div>
      )}
    </div>
  );
};

// ── 발행 내역 ────────────────────────────────────────────────────────────────
const CouponIssueList = () => {
  const [page, setPage] = React.useState(1);
  const [revoking, setRevoking] = React.useState(null);   // 회수할 발행분
  const list = useLive(() => api.live.coupons.issues({ page, size: COUPON_PAGE_SIZE }), [page]);
  const rows = couponRows(list.data);
  const total = couponTotal(list.data);
  const lastPage = total == null ? null : Math.max(1, Math.ceil(total / COUPON_PAGE_SIZE));
  return (
    <div className="table-wrap" data-loading={list.loading ? 'true' : 'false'}>
      <LiveBlock state={list} empty={rows.length === 0} emptyTitle="발행 내역이 없습니다">
        <table className="table" style={{ opacity: list.loading ? 0.45 : 1 }}>
          <thead><tr>
            <th>발행번호</th><th>쿠폰</th><th>받은 사람</th><th>할인</th><th>사용 기한</th><th>상태</th><th>발행일</th><th></th>
          </tr></thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r.couponIssueId}>
                <td className="t-mono">{r.couponIssueId}</td>
                <td style={{ fontWeight:600 }}>{r.couponName || EMPTY_VALUE}</td>
                <td>{r.nickName || EMPTY_VALUE}<span className="t-mono" style={{ color:'var(--text-3)' }}> #{r.uid}</span></td>
                <td>{discountText(r)}</td>
                <td style={{ fontSize:12.5, color:'var(--text-3)' }}>
                  {r.validEndDate ? `${ymd(r.validEndDate)}까지` : EMPTY_VALUE}
                </td>
                <td>
                  <StatusBadge map={COUPON_ISSUE_STATUS} value={r.status}/>
                  {r.status === 'REVOKED' && (
                    // 남의 지갑에서 뺀 일이라 누가·언제·왜가 보여야 합니다. 이름이 없으면(어드민 삭제) 번호로.
                    <div style={{ fontSize:11.5, color:'var(--text-3)', marginTop:4, lineHeight:1.5 }}>
                      {/* 이름은 그 어드민이 지워지면 null 입니다. 숫자만 날것으로 보여 주면 누군지 알 수 없으니
                          "알 수 없는 어드민"으로 적고, 번호는 마우스를 올리면 보이게 둡니다. */}
                      {r.revokedByName
                        ? r.revokedByName
                        : <span title={r.revokedBy != null ? `어드민 번호 ${r.revokedBy}` : ''}>알 수 없는 어드민</span>}
                      {r.revokedAt ? ` · ${fmtDT(r.revokedAt)}` : ''}
                      {r.revokeReason ? <div style={{ whiteSpace:'pre-wrap' }}>{r.revokeReason}</div> : null}
                    </div>
                  )}
                </td>
                <td style={{ fontSize:12.5, color:'var(--text-3)' }}>{r.regDate ? fmtDT(r.regDate) : EMPTY_VALUE}</td>
                <td>
                  {r.status === 'UNUSED' && (
                    // 미사용만 회수됩니다. 사용·만료·이미 회수된 건에는 버튼을 두지 않아 서버 거절을 만날 일이 없습니다.
                    <button className="btn btn-sm btn-danger-ghost" onClick={() => setRevoking(r)}>회수</button>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </LiveBlock>
      {revoking && (
        <CouponRevokeDialog issue={revoking}
          onClose={() => setRevoking(null)}
          onDone={() => { setRevoking(null); list.reload(); }}/>
      )}
      <div className="table-foot">
        <span style={{ fontSize:13, color:'var(--text-3)' }}>
          {total == null
            ? `${page}페이지 · ${rows.length}건 (서버가 총건수를 주지 않아 페이지 단위로 셉니다)`
            : `총 ${fmtNum(total)}건 · ${page}/${lastPage}페이지`}
        </span>
        <div style={{ display:'flex', gap:6 }}>
          <button className="btn btn-ghost btn-sm" disabled={page <= 1 || list.loading} onClick={() => setPage((p) => p - 1)}><Icon name="chevLeft" size={14}/>이전</button>
          <button className="btn btn-ghost btn-sm" disabled={list.loading || (lastPage == null ? rows.length < COUPON_PAGE_SIZE : page >= lastPage)} onClick={() => setPage((p) => p + 1)}>다음<Icon name="chevRight" size={14}/></button>
        </div>
      </div>
    </div>
  );
};

// 회수 — 남의 지갑에서 빼는 일이라 **사유를 반드시 받고**, 무엇을 누구에게서 회수하는지 보여 준 뒤 실행합니다.
const CouponRevokeDialog = ({ issue, onClose, onDone }) => {
  const [reason, setReason] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState('');
  const who = issue.nickName || `사용자 ${issue.uid}`;

  const submit = async () => {
    const r = reason.trim();
    if (!r) return;
    if (!window.confirm(
      `이 쿠폰을 회수할까요?\n\n받은 사람: ${who} (사용자 ${issue.uid})\n쿠폰: ${issue.couponName}\n할인: ${discountText(issue)}\n사유: ${r}\n\n`
      + `회수하면 그 사람 지갑에서 사라집니다. 필요하면 다시 발행할 수 있습니다.`)) return;
    setBusy(true); setError('');
    try {
      await api.live.coupons.revoke(issue.couponIssueId, r);
      onDone();
    } catch (e) { setError(errorText(e)); }
    finally { setBusy(false); }
  };

  return (
    <Drawer open onClose={onClose} title={`쿠폰 회수 · 발행 ${issue.couponIssueId}`}
      footer={<>
        <button className="btn btn-ghost" onClick={onClose} disabled={busy}>취소</button>
        <button className="btn btn-danger-ghost" onClick={submit} disabled={busy || !reason.trim()}>
          {busy ? '회수 중…' : '회수'}
        </button>
      </>}>
      {error && <div style={{ marginBottom:12 }}><Notice tone="danger">회수하지 못했습니다: {error}</Notice></div>}
      <div style={{ padding:12, borderRadius:8, background:'var(--bg-2, #fafafa)', fontSize:13, lineHeight:1.8, marginBottom:12 }}>
        <div>받은 사람 · <strong>{who}</strong> <span className="t-mono" style={{ color:'var(--text-3)' }}>#{issue.uid}</span></div>
        <div>쿠폰 · {issue.couponName}</div>
        <div>할인 · <strong>{discountText(issue)}</strong></div>
        <div>사용 기한 · {issue.validEndDate ? `${ymd(issue.validEndDate)}까지` : EMPTY_VALUE}</div>
      </div>
      <label style={{ display:'block', fontSize:13, fontWeight:600, marginBottom:6 }}>회수 사유 (필수)</label>
      <textarea className="input" style={{ width:'100%', height:80, padding:10, resize:'vertical' }}
        value={reason} maxLength={200} onChange={(e) => setReason(e.target.value)}
        placeholder="예: 중복 발행 / 대상이 아닌 사용자에게 발행"/>
      <div style={{ textAlign:'right', fontSize:12, color:'var(--text-3)' }}>{reason.length}/200</div>
      <div style={{ fontSize:12, color:'var(--text-3)', marginTop:8, lineHeight:1.6 }}>
        적으신 사유와 회수한 사람·시각이 발행 내역에 남습니다. 회수하면 <strong>손님 지갑에서도 사라집니다.</strong>
        이미 사용한 쿠폰은 회수할 수 없습니다.
      </div>
    </Drawer>
  );
};

Object.assign(window, { Coupons });
