// 멈춘 주문 — 결제까지 끝났는데 아무도 손대지 않은 주문 (GET /admin/orders/stuck, backend #319)
//
// 운영자가 이 화면에서 내리는 결정은 넷입니다. 칸은 그 넷을 가르는 데 필요한 것만 둡니다.
//   ① 취소하고 환불   ② 사장님에게 연락   ③ 픽업완료로 처리   ④ 그냥 둠
// ③ 은 서버에 어드민용 경로가 없어 이 화면에서 못 합니다 — 버튼을 만들지 않았습니다.
//
// 🔴 "환불 가능"이라는 말을 쓰지 않습니다.
//    adminCancellable 이 보는 것은 ① 주문 상태 ② PAY_CMPL 결제의 유무, 둘뿐이고
//    **토스 환불 가능 기간은 아직 들어 있지 않습니다**(2026-10-04 backend 명시).
//    그 값은 "지금 이 버튼을 눌러도 되는가"이지 "돈이 돌아온다"가 아닙니다. 버튼을 열고 닫는 데만 씁니다.
//
// 상세와 취소는 주문 관리의 OrderDrawer 를 그대로 씁니다(page-orders.jsx). 번들러가 없어
// 로드 순서가 곧 의존 순서입니다 — index.html 에서 이 파일은 page-orders.jsx **뒤**에 와야 합니다.

const STUCK_PAGE_SIZE = 20;

// 멈춤 기준을 운영자가 고르는 값. 서버 기본값이 따로 있어 "전체"면 파라미터를 안 보냅니다.
const STUCK_RANGES = [
  { value: '',      label: '전체 (서버 기준)' },
  { value: '60',    label: '1시간 이상' },
  { value: '1440',  label: '하루 이상' },
  { value: '4320',  label: '3일 이상' },
  { value: '10080', label: '7일 이상' },
];

// 멈춘 시간이 길수록 눈에 띄게. 숫자를 바꾸지 않고 색만 바꿉니다.
const stuckTone = (m) => (m >= 4320 ? 'danger' : m >= 1440 ? 'warn' : 'neutral');

const StuckOrders = ({ setRoute }) => {
  const [page, setPage] = React.useState(1);
  const [storeId, setStoreId] = React.useState('');
  const [minStuck, setMinStuck] = React.useState('');
  const [selected, setSelected] = React.useState(null);
  React.useEffect(() => { setPage(1); }, [storeId, minStuck]);

  const query = React.useMemo(() => {
    const q = { page, size: STUCK_PAGE_SIZE };
    if (storeId) q.storeId = Number(storeId);
    if (minStuck) q.minStuckMinutes = Number(minStuck);
    return q;
  }, [page, storeId, minStuck]);

  const list = useLive(() => api.live.orders.stuck(query), [JSON.stringify(query)]);
  useOrderChanged(list.reload);              // 주문을 취소하면 이 목록에서 빠집니다
  const rows = listRows(list.data);
  const total = listTotal(list.data);
  const lastPage = listLastPage(list.data, STUCK_PAGE_SIZE);
  const hasNext = lastPage == null ? rows.length === STUCK_PAGE_SIZE : page < lastPage;

  // 매장 필터 선택지. 승인된 매장만으로 충분합니다(멈춘 주문은 영업 중인 매장에서 납니다).
  const stores = useLive(() => api.live.stores.list('APPROVAL'), []);
  const storeOptions = listRows(stores.data);

  // 환불 상태를 두 갈래로 나눠 셉니다(backend #322).
  //  - 영구(MANUAL_REQUIRED): 배치가 재시도해도 안 됨 → 사람이 PG 콘솔에서. refundState 가 아직
  //    안 오는 서버(배포 전)에서는 옛 refundFailed 를 이 갈래로 봅니다(그때도 재시도 경로가 없었음).
  //  - 일시(RETRYING): 배치가 다시 시도함 → 운영자는 기다리면 됨. refundState 가 있을 때만 셉니다.
  const manualRefundCount = rows.filter((r) => (r.refundState ? r.refundState === 'MANUAL_REQUIRED' : r.refundFailed)).length;
  const retryingRefundCount = rows.filter((r) => r.refundState === 'RETRYING').length;

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>멈춘 주문</h1>
          <div className="sub">결제까지 끝났는데 아무도 손대지 않은 주문입니다 · 오래 멈춘 것부터 보여 줍니다</div>
        </div>
        <div className="actions">
          <button className="btn btn-ghost btn-sm" onClick={list.reload} disabled={list.loading}>
            <Icon name="refresh" size={14}/>새로고침
          </button>
        </div>
      </div>

      {(manualRefundCount > 0 || retryingRefundCount > 0) && (
        <div style={{ marginBottom: 12, display: 'grid', gap: 8 }}>
          {manualRefundCount > 0 && (
            <Notice tone="danger">
              이 페이지에 <strong>환불 불가 주문이 {fmtNum(manualRefundCount)}건</strong> 있습니다.
              취소는 기록됐지만 배치가 다시 시도해도 환불되지 않는 주문입니다 — 결제 쪽(PG 콘솔)에서
              직접 처리해야 합니다. 각 행의 코드를 확인하세요.
            </Notice>
          )}
          {retryingRefundCount > 0 && (
            <Notice>
              <strong>환불 재시도 중 {fmtNum(retryingRefundCount)}건</strong> — 취소는 걸렸고 배치가
              환불을 다시 시도합니다. 잠시 뒤 결제 상태가 "결제취소"로 바뀝니다. 지금 따로 할 일은 없습니다.
            </Notice>
          )}
        </div>
      )}

      <div className="table-wrap" data-loading={list.loading ? 'true' : 'false'}>
        <div className="table-toolbar" style={{ flexWrap: 'wrap' }}>
          <select className="select" aria-label="매장" value={storeId}
            onChange={(e) => setStoreId(e.target.value)}>
            <option value="">전체 매장</option>
            {storeOptions.map((s) => (
              <option key={s.storeId} value={s.storeId}>{s.name}</option>
            ))}
          </select>
          <select className="select" aria-label="멈춘 시간" value={minStuck}
            onChange={(e) => setMinStuck(e.target.value)}>
            {STUCK_RANGES.map((r) => <option key={r.value} value={r.value}>{r.label}</option>)}
          </select>
          {(storeId || minStuck) && (
            <button className="btn btn-ghost btn-sm" onClick={() => { setStoreId(''); setMinStuck(''); }}>초기화</button>
          )}
          {stores.error && (
            <span style={{ fontSize: 12.5, color: 'var(--text-3)' }}>매장 목록을 불러오지 못해 매장 필터가 비어 있습니다.</span>
          )}
        </div>

        <LiveBlock state={list} empty={rows.length === 0}
          emptyTitle={storeId || minStuck ? '조건에 맞는 멈춘 주문이 없습니다' : '멈춘 주문이 없습니다'}>
          <table className="table">
            <thead><tr>
              <th>멈춘 시간</th>
              <th>주문번호</th>
              <th>매장</th>
              <th>사용자</th>
              <th>금액</th>
              <th>주문 상태</th>
              <th>결제 상태</th>
              <th>자동 취소</th>
            </tr></thead>
            <tbody>
              {rows.map((o) => {
                const st = ORDER_STATUS[o.status] || { label: o.status, tone: 'neutral' };
                return (
                  <tr key={o.ordersId} onClick={() => setSelected(o.ordersId)} style={{ cursor: 'pointer' }}>
                    <td>
                      <Badge tone={stuckTone(Number(o.stuckMinutes))}>{fmtStuck(o.stuckMinutes)}</Badge>
                      <div style={{ fontSize: 11.5, color: 'var(--text-3)', marginTop: 3 }}
                        title="이 상태로 바뀐 시각입니다. 멈춘 시간은 여기서부터 셉니다.">
                        {fmtDT(o.statusChangedAt)}
                      </div>
                    </td>
                    <td className="t-mono">{o.ordersId}</td>
                    <td>
                      <div>{o.storeName || EMPTY_VALUE}</div>
                      {/* 사장님에게 연락하는 결정을 이 칸에서 바로 할 수 있게 번호를 같이 둡니다. */}
                      <div className="t-mono" style={{ fontSize: 11.5, color: 'var(--text-3)' }}>
                        {o.storePhoneNumber ? fmtPhone(o.storePhoneNumber) : '매장 연락처 없음'}
                      </div>
                    </td>
                    <td>
                      <div>{o.nickName || EMPTY_VALUE}</div>
                      <div className="t-mono" style={{ fontSize: 11.5, color: 'var(--text-3)' }}>
                        {o.phoneNumber ? maskPhone(o.phoneNumber) : EMPTY_VALUE}
                      </div>
                    </td>
                    <td style={{ fontWeight: 600 }}>{fmtKRW(o.totalAmount)}</td>
                    <td><Badge tone={st.tone} dot={st.dot}>{st.label}</Badge></td>
                    <td>
                      {o.paymentStatus
                        ? <StatusBadge map={PAYMENT_STATUS} value={o.paymentStatus}/>
                        : <span style={{ color: 'var(--text-3)' }}>{EMPTY_VALUE}</span>}
                      {refundView(o) && (
                        <div style={{ marginTop: 4 }}>
                          <Badge tone={refundView(o).tone}>{refundView(o).label}</Badge>
                          {/* 코드 원문은 지어내지 않고 서버가 준 것만. MANUAL_REQUIRED 면 PG 콘솔의 유일한 단서입니다. */}
                          {o.refundFailCode && (
                            <div className="t-mono" style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2 }}
                              title={o.refundFailMessage || ''}>{o.refundFailCode}</div>
                          )}
                        </div>
                      )}
                    </td>
                    <td>
                      {/* 서버가 사유 문장을 주므로 화면이 지어내지 않습니다. "왜 얘는 자동으로 안 지워지지?"의 답입니다. */}
                      {o.autoCancelEligible
                        ? <Badge tone="warn">대상</Badge>
                        : <span style={{ fontSize: 12.5, color: 'var(--text-3)' }} title={o.autoCancelReason || ''}>
                            대상 아님
                          </span>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </LiveBlock>

        {!list.error && (
          <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, marginLeft: 'auto' }}>
              <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 || !hasNext}
                onClick={() => setPage((p) => p + 1)}>다음<Icon name="chevRight" size={14}/></button>
            </div>
          </div>
        )}
      </div>

      {/* 상세·취소는 주문 관리와 같은 드로어입니다 — 취소 규칙과 환불 경고를 한 곳에서만 관리합니다. */}
      {selected != null && (
        <OrderDrawer ordersId={selected} onClose={() => setSelected(null)} onChanged={list.reload}/>
      )}
    </div>
  );
};

Object.assign(window, { StuckOrders, STUCK_PAGE_SIZE });
