/** @jsxRuntime classic */
// 관리자 화면과 팀 조회 화면이 같이 쓰는 것: 서버 호출, 숫자·날짜 표시, 알림, 머리글, 대화상자, 아이콘.
// Babel 이 일반 스크립트로 실행하므로 여기의 최상위 선언은 다음 스크립트(admin.jsx 등)에서 그대로 보인다.
// classic 런타임을 강제한다. automatic 이면 react/jsx-runtime 을 찾는데 UMD 빌드에는 없어 화면이 그려지지 않는다.
const { useState, useEffect, useRef, useMemo, useCallback, useContext, createContext, Fragment } = React;

// ---- 서버 호출 ----
class ApiError extends Error {
  constructor(message, status, data) { super(message); this.status = status; this.data = data || {}; }
}

// 응답이 { ok:false } 면 서버가 준 한글 메시지로 ApiError 를 던진다. 401(로그인 필요)은 onUnauthorized 로도 알린다.
const apiEvents = { onUnauthorized: null };
async function api(method, path, body) {
  let res;
  try {
    res = await fetch(path, {
      method,
      headers: body === undefined ? {} : { 'Content-Type': 'application/json' },
      body: body === undefined ? undefined : JSON.stringify(body),
      cache: 'no-store',
      credentials: 'same-origin',
    });
  } catch {
    throw new ApiError('서버에 연결하지 못했습니다. 인터넷 연결을 확인해 주세요', 0);
  }
  let data = null;
  try { data = await res.json(); } catch {}
  if (!data || !data.ok) {
    if (res.status === 401 && data && data.needLogin && apiEvents.onUnauthorized) apiEvents.onUnauthorized();
    throw new ApiError((data && data.error) || `요청이 실패했습니다 (${res.status})`, res.status, data);
  }
  return data;
}

// ---- 표시 ----
const fmt = n => (n == null || n === '' ? '' : Number(n).toLocaleString('ko-KR'));
const won = n => (n == null ? '-' : `${Number(n).toLocaleString('ko-KR')}원`);
const kstDateTime = iso => new Date(iso).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' });
const kstTime = date => date.toLocaleTimeString('ko-KR', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', second: '2-digit' });
// 한국 시간 기준 오늘 {year, month, date:'YYYY-MM-DD'}
function kstToday() {
  const s = new Date().toLocaleDateString('sv-SE', { timeZone: 'Asia/Seoul' });
  return { year: Number(s.slice(0, 4)), month: Number(s.slice(5, 7)), date: s };
}
const shortDate = d => (d ? `${Number(d.slice(5, 7))}.${Number(d.slice(8, 10))}` : '날짜 없음');
const pad2 = n => String(n).padStart(2, '0');
const cx = (...a) => a.filter(Boolean).join(' ');

// 입력칸 글자 → 정수 또는 null (빈칸). 숫자가 아니면 NaN
function parseIntInput(s) {
  if (s == null) return null;
  const t = String(s).replace(/[,\s]/g, '');
  if (t === '') return null;
  return /^-?\d+$/.test(t) ? Number(t) : NaN;
}
const toInput = n => (n == null ? '' : fmt(n));

function downloadBlob(blob, name) {
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = name;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(a.href), 10000);
}

async function copyText(text) {
  try {
    if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; }
  } catch {}
  const ta = document.createElement('textarea');
  ta.value = text;
  ta.style.position = 'fixed';
  ta.style.opacity = '0';
  document.body.appendChild(ta);
  ta.select();
  let done = false;
  try { done = document.execCommand('copy'); } catch {}
  ta.remove();
  return done;
}

// ---- 알림 ----
const ToastContext = createContext(null);
function ToastProvider({ children }) {
  const [items, setItems] = useState([]);
  const push = useCallback((text, kind) => {
    const id = Math.random().toString(36).slice(2);
    setItems(list => [...list.slice(-2), { id, text, kind }]);
    setTimeout(() => setItems(list => list.filter(t => t.id !== id)), kind === 'err' ? 6000 : 2800);
  }, []);
  const toast = useMemo(() => ({ ok: t => push(t, 'ok'), err: t => push(t, 'err') }), [push]);
  return (
    <ToastContext.Provider value={toast}>
      {children}
      <div className="toasts" role="status" aria-live="polite">
        {items.map(t => <div key={t.id} className={cx('toast', t.kind === 'err' && 'err')}>{t.text}</div>)}
      </div>
    </ToastContext.Provider>
  );
}
const useToast = () => useContext(ToastContext);


// ---- 화면 안 이동 ----
// 사이트 주소는 하나(/)다. # 뒤가 ADMIN_ROUTES 중 하나면 관리 화면, 아니면 팀 조회 화면이다.
const ADMIN_ROUTES = ['teams', 'team', 'bulk', 'files', 'log', 'settings'];
// 팀에 보내는 접속 링크(/?mode=user)로 들어왔는지. 그러면 "관리" 전환을 보이지 않는다(권한은 서버가 따로 확인한다)
const isSharedLink = new URLSearchParams(location.search).get('mode') === 'user';

function parseHash() {
  const [path, query = ''] = location.hash.replace(/^#\/?/, '').split('?');
  const parts = path.split('/').filter(Boolean);
  return { name: parts[0] || '', id: parts[1] ? decodeURIComponent(parts[1]) : null, q: Object.fromEntries(new URLSearchParams(query)) };
}
function useRoute() {
  const [route, setRoute] = useState(parseHash);
  useEffect(() => {
    const onHash = () => setRoute(parseHash());
    window.addEventListener('hashchange', onHash);
    return () => window.removeEventListener('hashchange', onHash);
  }, []);
  return route;
}
const go = hash => { location.hash = hash; };

// 저장하지 않은 입력이 있으면 이동 전에 묻는다. 화면의 입력 묶음마다 key 로 표시한다.
const dirtyForms = new Set();
function useDirty(key, dirty) {
  useEffect(() => {
    if (dirty) dirtyForms.add(key); else dirtyForms.delete(key);
    return () => dirtyForms.delete(key);
  }, [key, dirty]);
}
// keep: 이동해도 화면에 그대로 남는 입력 묶음(예: 월만 바꿀 때의 연도 정보)은 묻지 않는다
const confirmLeave = (keep = () => false) =>
  ![...dirtyForms].some(k => !keep(k)) || confirm('저장하지 않은 입력이 있습니다. 버리고 이동할까요?');
window.addEventListener('beforeunload', e => { if (dirtyForms.size) { e.preventDefault(); e.returnValue = ''; } });

// ---- 머리글 (팀 조회·관리 공용) ----
// mode: 'team' | 'admin'. actions 는 오른쪽 끝에 놓을 버튼들, nav 는 머리글 아래 줄(관리 메뉴)
function TopBar({ mode, actions, nav }) {
  const switchTo = next => {
    if (next === mode || !confirmLeave()) return;
    go(next === 'admin' ? '/teams' : '/');
  };
  return (
    <header className="topbar">
      <div className="topbar-in">
        <div className="brand">조직관리비</div>
        {!isSharedLink && (
          <div className="seg" role="group" aria-label="화면 고르기">
            <button type="button" aria-pressed={mode === 'team'} onClick={() => switchTo('team')}>팀 조회</button>
            <button type="button" aria-pressed={mode === 'admin'} onClick={() => switchTo('admin')}>관리</button>
          </div>
        )}
        {actions}
      </div>
      {nav}
    </header>
  );
}

// ---- 입력 ----
// 금액·인원 입력칸. 입력하는 동안 천 단위 쉼표를 붙인다. value/onChange 는 글자(빈칸 허용)다.
const NumberInput = React.forwardRef(function NumberInput({ value, onChange, allowNegative = false, className, ...rest }, ref) {
  const handle = e => {
    let s = e.target.value.replace(/[^\d-]/g, '');
    const neg = allowNegative && s.startsWith('-');
    s = s.replace(/-/g, '').replace(/^0+(?=\d)/, '');
    onChange((neg ? '-' : '') + (s ? Number(s).toLocaleString('ko-KR') : ''));
  };
  return <input {...rest} ref={ref} className={cx('input num', className)} inputMode={allowNegative ? 'text' : 'numeric'} value={value} onChange={handle} autoComplete="off" />;
});

// ---- 대화상자 ----
function Modal({ title, onClose, children, actions, busy }) {
  const ref = useRef(null);
  useEffect(() => {
    const onKey = e => { if (e.key === 'Escape' && !busy) onClose(); };
    document.addEventListener('keydown', onKey);
    const first = ref.current && ref.current.querySelector('input:not([type=checkbox]), select, textarea');
    if (first) first.focus();
    return () => document.removeEventListener('keydown', onKey);
  }, [busy]);
  return (
    <div className="backdrop" onMouseDown={e => { if (e.target === e.currentTarget && !busy) onClose(); }}>
      <div className="dialog" role="dialog" aria-modal="true" aria-label={title} ref={ref}>
        <h2>{title}</h2>
        {children}
        {actions && <div className="actions">{actions}</div>}
      </div>
    </div>
  );
}

// ---- 아이콘 (선 그림) ----
const svg = path => props => (
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}>{path}</svg>
);
const Icon = {
  Link: svg(<><path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7" /><path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7" /></>),
  Refresh: svg(<><path d="M21 12a9 9 0 1 1-2.6-6.4" /><path d="M21 3v6h-6" /></>),
  Grip: svg(<><circle cx="9" cy="6" r="1" /><circle cx="15" cy="6" r="1" /><circle cx="9" cy="12" r="1" /><circle cx="15" cy="12" r="1" /><circle cx="9" cy="18" r="1" /><circle cx="15" cy="18" r="1" /></>),
  Up: svg(<path d="M6 15l6-6 6 6" />),
  Down: svg(<path d="M6 9l6 6 6-6" />),
  Back: svg(<path d="M15 18l-6-6 6-6" />),
  Logout: svg(<><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><path d="M16 17l5-5-5-5M21 12H9" /></>),
};
