"use client";

import { useEffect, useMemo, useState, useCallback, useRef } from "react";
import { Check, Download, X, RefreshCcw, Truck, AlertCircle, Package, PhoneCall } from "lucide-react";
import Swal from "sweetalert2";
import { captureAndShare } from "@/lib/capture-share";
import { Loading } from "@/components/ui/loading";
import { SearchableSelect } from "@/components/ui/searchable-select";

interface Order {
  OBJID: string; ORDER_NO: string; ORDER_DATE: string;
  COMPANY_NAME: string; EMAIL: string; STATUS: string;
  TOTAL_TAXFREE: number; TOTAL_TAXABLE: number;
  TOTAL_SUPPLY: number; TOTAL_VAT: number; TOTAL_AMOUNT: number;
  EDITING_BY?: string | null;
  EDITING_BY_NAME?: string | null;
}
interface DetailOrder extends Order {
  CEO_NAME?: string; BIZ_NO?: string; PHONE?: string; ADDRESS?: string;
  MEMO?: string; APPROVE_DATE?: string;
  CUSTOMER_OBJID?: string;
  EDITING_BY?: string | null;
  EDITING_BY_NAME?: string | null;
}
interface DetailLine {
  OBJID: string;
  SEQ: number; ITEM_NAME: string; UNIT: string; UNIT_PRICE: number; QTY: number;
  IS_TAX_FREE: string; SUPPLY_AMOUNT: number; VAT_AMOUNT: number; TOTAL_AMOUNT: number;
  STOCK_QTY: number;
  KIND: "ITEM" | "DELIVERY" | "CHARTER" | "REFUND";
  EXTRA_LABEL?: string;
  REMARK?: string;
}
interface Supplier {
  NAME: string; CEO: string; BIZ_NO: string;
  BANK_ACCOUNT: string; PHONE: string; EMAIL: string; ADDRESS: string;
}

const fmt = (n: number | string | undefined | null) =>
  Number(n || 0).toLocaleString("ko-KR");

const STATUS_LABEL: Record<string, string> = {
  REQUESTED: "출고요청",
  APPROVED: "출고완료",
  PAID: "입금완료",
  INVOICED: "계산서발행",
  CANCELLED: "취소",
};
const STATUS_COLOR: Record<string, string> = {
  REQUESTED: "bg-amber-100 text-amber-700 border-amber-200",
  APPROVED: "bg-blue-100 text-blue-700 border-blue-200",
  PAID: "bg-emerald-100 text-emerald-700 border-emerald-200",
  INVOICED: "bg-violet-100 text-violet-700 border-violet-200",
  CANCELLED: "bg-slate-100 text-slate-500 border-slate-200",
};

const todayStr = () => {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};
const oneMonthAgoStr = () => {
  const d = new Date();
  d.setMonth(d.getMonth() - 1);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
};

export default function AdminOrdersPage() {
  const [orders, setOrders] = useState<Order[]>([]);
  // URL 쿼리 우선: dateFrom/dateTo 키가 있으면 그 값 사용 (빈 문자열도 명시적 = 전체 기간).
  // 키가 없을 때 기본값 — 한 달 전 ~ 오늘
  const initial = (() => {
    // 기본 status = "EDITABLE" → REQUESTED + APPROVED + PAID (출고요청 + 출고완료 + 입금완료) 노출
    // (admin 은 입금완료 발주도 품목/택배/용차 추가·수정 가능하므로 기본 노출 대상에 포함)
    if (typeof window === "undefined") return { status: "EDITABLE", dateFrom: oneMonthAgoStr(), dateTo: todayStr(), keyword: "" };
    const q = new URLSearchParams(window.location.search);
    return {
      status: q.get("status") ?? "EDITABLE",
      dateFrom: q.has("dateFrom") ? (q.get("dateFrom") ?? "") : oneMonthAgoStr(),
      dateTo: q.has("dateTo") ? (q.get("dateTo") ?? "") : todayStr(),
      keyword: q.get("keyword") ?? "",
    };
  })();
  const [status, setStatus] = useState(initial.status);
  const [dateFrom, setDateFrom] = useState(initial.dateFrom);
  const [dateTo, setDateTo] = useState(initial.dateTo);
  const [keyword, setKeyword] = useState(initial.keyword);
  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [activeId, setActiveId] = useState<string>("");
  const [detail, setDetail] = useState<{ order: DetailOrder; items: DetailLine[]; supplier: Supplier } | null>(null);
  const [loading, setLoading] = useState(false);
  // 발주 리스트 보기 모드 — 'list'(테이블) | 'card'(카드)
  const [listViewMode, setListViewMode] = useState<"list" | "card">("list");
  const [busy, setBusy] = useState(false);
  // 편집 락: 내가 보유 / 다른 사람이 보유 / 미보유
  const [lockedByMe, setLockedByMe] = useState(false);
  const [lockedByOther, setLockedByOther] = useState<{ name: string } | null>(null);
  const [myUserId, setMyUserId] = useState<string>("");
  const heartbeatRef = useRef<NodeJS.Timeout | null>(null);
  const lockedOrderRef = useRef<string>("");  // 현재 락 보유 중인 발주 id
  // 최근 release 한 발주 5초간 추적 — 자동 list 갱신이 가져오는 stale me 결과를 무시하기 위함
  const recentlyReleasedRef = useRef<Set<string>>(new Set());

  // 본인 user_id 1회 확보
  useEffect(() => {
    fetch("/api/auth/me").then((r) => r.json()).then((d) => {
      if (d?.user) setMyUserId(String(d.user.objid || d.user.userId));
    }).catch(() => {});
  }, []);

  // 락 해제 헬퍼 — recentlyReleased 에 5초간 등록해서 자동 갱신의 stale 결과를 무시
  const releaseLock = useCallback(async (orderObjid: string) => {
    if (!orderObjid) return;
    recentlyReleasedRef.current.add(orderObjid);
    setTimeout(() => recentlyReleasedRef.current.delete(orderObjid), 5000);
    try {
      await fetch("/api/m/admin/orders/lock", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ orderObjid, action: "release" }),
        keepalive: true,
      });
    } catch { /* ignore */ }
  }, []);

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const res = await fetch("/api/m/orders/list", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          status: status === "EDITABLE" ? undefined : (status || undefined),
          statuses: status === "EDITABLE" ? ["REQUESTED", "APPROVED", "PAID"] : undefined,
          dateFrom: dateFrom || undefined,
          dateTo: dateTo || undefined,
          keyword: keyword || undefined,
        }),
      });
      const j = await res.json();
      const list: Order[] = j.RESULTLIST ?? [];
      // 클라이언트 락 추적과 list 결과 정합화:
      // - 최근 release 한 발주는 DB stale (me 로 보이는 경우) 무시 → null 처리
      // - 내가 보유 중인 발주는 me 로 보정 (혹시 누락된 경우)
      const released = recentlyReleasedRef.current;
      const myLocked = lockedOrderRef.current;
      const adjusted: Order[] = list.map((o) => {
        if (released.has(o.OBJID)) {
          return { ...o, EDITING_BY: null, EDITING_BY_NAME: null };
        }
        if (myLocked && o.OBJID === myLocked) {
          return {
            ...o,
            EDITING_BY: myUserId || (o.EDITING_BY ?? "me"),
            EDITING_BY_NAME: o.EDITING_BY_NAME || "(나)",
          };
        }
        return o;
      });
      setOrders(adjusted);
      setSelected((prev) => new Set(Array.from(prev).filter((id) => adjusted.some((o) => o.OBJID === id))));
      if (adjusted.length && !adjusted.some((o) => o.OBJID === activeId)) {
        setActiveId(adjusted[0].OBJID);
      } else if (!adjusted.length) {
        setActiveId("");
        setDetail(null);
      }
    } finally {
      setLoading(false);
    }
  }, [status, dateFrom, dateTo, keyword, activeId, myUserId]);

  // 최초 로드만 자동, 검색 조건 변경은 [조회] 버튼으로
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => { load(); }, [load]);

  // 발주 리스트 30초마다 자동 갱신 — 락 상태(누가 수정 중) 실시간 반영
  useEffect(() => {
    const id = setInterval(() => { load(); }, 30000);
    return () => clearInterval(id);
  }, [load]);

  const reloadDetail = useCallback(async () => {
    if (!activeId) { setDetail(null); return; }
    const res = await fetch("/api/m/orders/detail", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ objid: activeId }),
    });
    const j = await res.json();
    if (j.success) {
      setDetail({ order: j.order as DetailOrder, items: j.items as DetailLine[], supplier: j.supplier as Supplier });
    }
  }, [activeId]);

  // 활성 행 변경 시 상세 로드 + 편집 락 획득/해제
  useEffect(() => {
    let cancelled = false;
    // 이전 락 해제 (다른 발주로 이동했을 때)
    const previousLocked = lockedOrderRef.current;
    if (previousLocked && previousLocked !== activeId) {
      releaseLock(previousLocked);
      // 옵티미스틱: 리스트에서 이전 락 즉시 제거
      setOrders((prev) => prev.map((o) =>
        o.OBJID === previousLocked ? { ...o, EDITING_BY: null, EDITING_BY_NAME: null } : o
      ));
      lockedOrderRef.current = "";
    }
    // 이전 heartbeat 정지
    if (heartbeatRef.current) { clearInterval(heartbeatRef.current); heartbeatRef.current = null; }
    setLockedByMe(false);
    setLockedByOther(null);

    (async () => {
      if (!activeId) { setDetail(null); return; }
      // 1) 상세 로드
      const res = await fetch("/api/m/orders/detail", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ objid: activeId }),
      });
      const j = await res.json();
      if (cancelled) return;
      if (j.success) {
        setDetail({ order: j.order as DetailOrder, items: j.items as DetailLine[], supplier: j.supplier as Supplier });
      }

      // 2) 편집 락 시도
      try {
        const lockRes = await fetch("/api/m/admin/orders/lock", {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ orderObjid: activeId, action: "acquire" }),
        });
        const lj = await lockRes.json();
        if (cancelled) return;
        if (lj.success) {
          setLockedByMe(true);
          setLockedByOther(null);
          lockedOrderRef.current = activeId;
          // 옵티미스틱: 리스트에 내 락 즉시 반영
          setOrders((prev) => prev.map((o) =>
            o.OBJID === activeId ? { ...o, EDITING_BY: lj.lockedBy || myUserId, EDITING_BY_NAME: lj.lockedByName || "(나)" } : o
          ));
          // 30초마다 heartbeat
          heartbeatRef.current = setInterval(async () => {
            try {
              const hr = await fetch("/api/m/admin/orders/lock", {
                method: "POST", headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ orderObjid: activeId, action: "heartbeat" }),
              });
              const hj = await hr.json();
              if (!hj.success) {
                // 락 뺏김
                setLockedByMe(false);
                setLockedByOther(hj.lockedByName ? { name: hj.lockedByName } : { name: "다른 담당자" });
                if (heartbeatRef.current) { clearInterval(heartbeatRef.current); heartbeatRef.current = null; }
              }
            } catch { /* ignore */ }
          }, 30000);
        } else if (lj.locked) {
          setLockedByMe(false);
          setLockedByOther({ name: lj.lockedByName || lj.lockedBy || "다른 담당자" });
          // 옵티미스틱: 리스트에 락 정보 반영
          setOrders((prev) => prev.map((o) =>
            o.OBJID === activeId ? { ...o, EDITING_BY: lj.lockedBy || "other", EDITING_BY_NAME: lj.lockedByName || "다른 담당자" } : o
          ));
          Swal.fire({
            icon: "warning",
            title: "수정 중인 발주",
            text: lj.message || `${lj.lockedByName} 님이 수정 중입니다.`,
            confirmButtonColor: "#0f766e",
            confirmButtonText: "확인",
          });
        }
      } catch { /* ignore */ }
    })();

    return () => { cancelled = true; };
  }, [activeId, releaseLock, myUserId]);

  // 페이지 떠날 때 락 해제 (best-effort) — sendBeacon 으로 keepalive
  useEffect(() => {
    const cleanup = () => {
      const id = lockedOrderRef.current;
      if (!id) return;
      try {
        const body = JSON.stringify({ orderObjid: id, action: "release" });
        if (navigator.sendBeacon) {
          navigator.sendBeacon("/api/m/admin/orders/lock", new Blob([body], { type: "application/json" }));
        } else {
          fetch("/api/m/admin/orders/lock", { method: "POST", headers: { "Content-Type": "application/json" }, body, keepalive: true });
        }
      } catch { /* ignore */ }
    };
    window.addEventListener("beforeunload", cleanup);
    return () => {
      window.removeEventListener("beforeunload", cleanup);
      // 컴포넌트 언마운트 시도 정리
      cleanup();
      if (heartbeatRef.current) { clearInterval(heartbeatRef.current); heartbeatRef.current = null; }
    };
  }, []);

  const requestedSelectedIds = useMemo(
    () => Array.from(selected).filter((id) => orders.find((o) => o.OBJID === id)?.STATUS === "REQUESTED"),
    [selected, orders]
  );

  const toggleOne = (o: Order) => {
    if (o.STATUS !== "REQUESTED") return;
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(o.OBJID)) next.delete(o.OBJID); else next.add(o.OBJID);
      return next;
    });
  };
  const toggleAllRequested = () => {
    const reqIds = orders.filter((o) => o.STATUS === "REQUESTED").map((o) => o.OBJID);
    setSelected((prev) => {
      const allOn = reqIds.length > 0 && reqIds.every((id) => prev.has(id));
      const next = new Set(prev);
      if (allOn) reqIds.forEach((id) => next.delete(id));
      else reqIds.forEach((id) => next.add(id));
      return next;
    });
  };

  const bulkShip = async () => {
    const ids = requestedSelectedIds;
    if (ids.length === 0) {
      Swal.fire({ icon: "warning", title: "출고 처리할 발주를 선택하세요.", text: "체크박스로 출고요청 상태의 발주를 선택해주세요." });
      return;
    }
    const ok = await Swal.fire({
      icon: "question",
      title: `${ids.length}건 출고 처리하시겠습니까?`,
      html: `선택된 발주의 재고가 차감되고,<br>각 거래처 이메일로 거래명세표가 발송됩니다.`,
      showCancelButton: true,
      confirmButtonText: "출고",
      cancelButtonText: "취소",
      confirmButtonColor: "#0f766e",
    });
    if (!ok.isConfirmed) return;

    setBusy(true);
    let ok_cnt = 0;
    let fail_cnt = 0;
    let mailFail = 0;
    const errors: string[] = [];
    for (const objid of ids) {
      try {
        const res = await fetch("/api/m/orders/approve", {
          method: "POST", headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ objid }),
        });
        const j = await res.json();
        if (j.success) {
          ok_cnt++;
          if (!j.mailSent) mailFail++;
        } else {
          fail_cnt++;
          const o = orders.find((x) => x.OBJID === objid);
          errors.push(`${o?.ORDER_NO ?? objid}: ${j.message ?? "실패"}`);
        }
      } catch {
        fail_cnt++;
      }
    }
    setBusy(false);
    setSelected(new Set());
    await load();

    Swal.fire({
      icon: fail_cnt === 0 ? "success" : "warning",
      title: `출고 처리 완료 (성공 ${ok_cnt} / 실패 ${fail_cnt})`,
      html: [
        ok_cnt > 0 ? `· 거래명세표 메일 ${ok_cnt - mailFail}건 발송${mailFail > 0 ? ` <span style="color:#dc2626">(${mailFail}건 메일 실패)</span>` : ""}` : "",
        errors.length > 0 ? `<br><br><b>실패 내역:</b><br>${errors.join("<br>")}` : "",
      ].filter(Boolean).join(""),
    });
  };

  const cancelOne = async (o: Order) => {
    const ok = await Swal.fire({
      icon: "warning", title: "발주를 취소(반려)하시겠습니까?",
      text: o.ORDER_NO, showCancelButton: true, confirmButtonColor: "#dc2626",
    });
    if (!ok.isConfirmed) return;
    const res = await fetch("/api/m/orders/cancel", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ objid: o.OBJID }),
    });
    if ((await res.json()).success) load();
  };

  const allRequestedCount = orders.filter((o) => o.STATUS === "REQUESTED").length;
  const allRequestedChecked = allRequestedCount > 0 && allRequestedCount === requestedSelectedIds.length;

  return (
    <div className="space-y-3">
      {busy && <Loading message="처리 중..." />}
      {/* 헤더 */}
      <div className="flex items-end justify-between flex-wrap gap-2">
        <div>
          <h1 className="text-xl font-bold text-slate-900">출고 처리</h1>
          <p className="text-xs text-slate-500 mt-0.5">
            발주를 선택하면 거래명세표가 미리보기로 표시됩니다. 체크박스로 다중 선택 후 [출고]를 누르면 일괄 처리됩니다.
          </p>
        </div>
        <div className="flex items-center gap-2">
          <ManualOrderButton onCreated={async (newObjid) => { await load(); setActiveId(newObjid); }} />
          <button
            onClick={bulkShip}
            disabled={busy || requestedSelectedIds.length === 0}
            className="h-9 px-4 rounded-lg bg-emerald-700 text-white text-sm font-bold hover:bg-emerald-800 disabled:opacity-40 disabled:cursor-not-allowed inline-flex items-center gap-1.5 shadow-sm"
          >
            <Truck size={14} /> 출고{requestedSelectedIds.length > 0 ? ` (${requestedSelectedIds.length})` : ""}
          </button>
        </div>
      </div>

      {/* 검색바 — 모바일에선 한 줄에 핵심만, 데스크탑에선 여유 있게 */}
      <div className="bg-white border border-slate-200 rounded-xl p-2 sm:p-3">
        <div className="flex flex-wrap gap-1.5 sm:gap-2 items-center text-xs">
          <input type="date" value={dateFrom} onChange={(e) => setDateFrom(e.target.value)}
            className="h-8 sm:h-9 px-2 rounded border border-slate-200 text-xs sm:text-sm min-w-0 flex-1 sm:flex-none sm:w-[140px]" />
          <span className="text-slate-400 text-[10px] sm:text-xs">~</span>
          <input type="date" value={dateTo} onChange={(e) => setDateTo(e.target.value)}
            className="h-8 sm:h-9 px-2 rounded border border-slate-200 text-xs sm:text-sm min-w-0 flex-1 sm:flex-none sm:w-[140px]" />
          <select value={status} onChange={(e) => setStatus(e.target.value)}
            className="h-8 sm:h-9 px-2 rounded border border-slate-200 text-xs sm:text-sm bg-white sm:w-[180px]">
            <option value="EDITABLE">출고요청+출고완료+입금완료</option>
            <option value="">전체 상태</option>
            {Object.entries(STATUS_LABEL).map(([k, v]) => <option key={k} value={k}>{v}</option>)}
          </select>
          <input type="text" value={keyword} onChange={(e) => setKeyword(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") load(); }}
            placeholder="발주번호 / 업체"
            className="h-8 sm:h-9 px-2 rounded border border-slate-200 text-xs sm:text-sm flex-1 min-w-[120px]" />
          <button onClick={load} disabled={loading}
            className="h-8 sm:h-9 px-3 rounded bg-slate-800 text-white text-xs sm:text-sm font-bold hover:bg-slate-900 disabled:opacity-50">
            조회
          </button>
          {(dateFrom !== oneMonthAgoStr() || dateTo !== todayStr() || status !== "EDITABLE" || keyword) && (
            <button onClick={() => { setDateFrom(oneMonthAgoStr()); setDateTo(todayStr()); setStatus("EDITABLE"); setKeyword(""); }}
              className="h-8 sm:h-9 px-2 rounded border border-slate-200 bg-white text-slate-600 text-[11px]">
              초기화
            </button>
          )}
        </div>
      </div>

      {/* 2분할 레이아웃 — 모바일은 stack, lg 이상은 좌-우 분할 */}
      <div className="grid grid-cols-1 lg:grid-cols-[minmax(640px,1fr)_minmax(0,1.3fr)] gap-3 lg:min-h-[calc(100vh-200px)]">
        {/* 좌측: 발주 리스트 */}
        <div className="bg-white border border-slate-200 rounded-xl overflow-hidden flex flex-col">
          <div className="px-3 py-2 bg-slate-50 border-b border-slate-200 text-xs font-semibold text-slate-600 flex items-center justify-between gap-2">
            <span>발주 리스트 ({orders.length}건)</span>
            <div className="inline-flex rounded border border-slate-300 overflow-hidden">
              <button onClick={() => setListViewMode("list")}
                className={`px-2 py-0.5 text-[10px] ${listViewMode === "list" ? "bg-slate-700 text-white" : "bg-white text-slate-600 hover:bg-slate-100"}`}>리스트</button>
              <button onClick={() => setListViewMode("card")}
                className={`px-2 py-0.5 text-[10px] border-l border-slate-300 ${listViewMode === "card" ? "bg-slate-700 text-white" : "bg-white text-slate-600 hover:bg-slate-100"}`}>카드</button>
            </div>
            <span className="text-slate-400 font-normal">선택 {selected.size} / 출고가능 {allRequestedCount}</span>
          </div>
          <div className="flex-1 lg:overflow-auto max-h-[60vh] lg:max-h-none overflow-x-hidden">
            {listViewMode === "card" ? (
              orders.length === 0 ? (
                <div className="text-center py-12 text-slate-400">{loading ? "조회 중..." : "발주가 없습니다."}</div>
              ) : (
                <div className="grid grid-cols-1 sm:grid-cols-2 gap-2 p-2">
                  {orders.map((o) => {
                    const checked = selected.has(o.OBJID);
                    const active = o.OBJID === activeId;
                    const lockedByMeRow = !!o.EDITING_BY && o.EDITING_BY === myUserId;
                    const lockedByOtherUser = !!o.EDITING_BY && o.EDITING_BY !== myUserId;
                    return (
                      <div
                        key={o.OBJID}
                        onClick={() => setActiveId(o.OBJID)}
                        className={`border rounded-lg p-3 cursor-pointer transition ${active ? "border-emerald-500 bg-emerald-50/60 shadow-sm" : lockedByMeRow ? "border-emerald-300 bg-emerald-50/30" : lockedByOtherUser ? "border-rose-200 bg-rose-50/40" : "border-slate-200 bg-white hover:bg-slate-50"}`}
                      >
                        <div className="flex items-center justify-between gap-2 mb-1.5">
                          <div className="flex items-center gap-2 min-w-0">
                            <input
                              type="checkbox"
                              checked={checked}
                              onClick={(e) => e.stopPropagation()}
                              onChange={() => toggleOne(o)}
                              disabled={o.STATUS !== "REQUESTED"}
                              className="accent-emerald-600 disabled:opacity-30"
                            />
                            <span className="font-bold text-base text-slate-800 truncate" title={o.COMPANY_NAME}>{o.COMPANY_NAME}</span>
                            {lockedByMeRow && (
                              <span className="text-[10px] font-bold text-emerald-700" title="내가 수정 중">✏️</span>
                            )}
                            {lockedByOtherUser && (
                              <span className="text-[10px] font-bold text-rose-700" title={`${o.EDITING_BY_NAME} 수정 중`}>🔒</span>
                            )}
                          </div>
                          <span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold border whitespace-nowrap ${STATUS_COLOR[o.STATUS] ?? "bg-slate-100 text-slate-500 border-slate-200"}`}>
                            {STATUS_LABEL[o.STATUS] ?? o.STATUS}
                          </span>
                        </div>
                        <div className="text-[11px] text-slate-500 flex items-center gap-1.5">
                          <span>{o.ORDER_DATE}</span>
                          <span className="text-slate-300">·</span>
                          <span className="font-semibold text-slate-600">{o.ORDER_NO}</span>
                        </div>
                        {lockedByMeRow && (
                          <div className="text-[10px] font-bold text-emerald-700 mt-0.5">✏️ 내가 수정 중</div>
                        )}
                        {lockedByOtherUser && (
                          <div className="text-[10px] font-bold text-rose-700 mt-0.5">🔒 {o.EDITING_BY_NAME} 수정 중</div>
                        )}
                        <div className="text-right tabular-nums font-bold text-emerald-700 mt-1">₩{fmt(o.TOTAL_AMOUNT)}</div>
                      </div>
                    );
                  })}
                </div>
              )
            ) : (
            <table className="w-full text-xs table-fixed">
              <thead className="bg-slate-50 text-slate-600 sticky top-0 z-10">
                <tr>
                  <th className="w-8 px-1.5 py-2">
                    <input type="checkbox" checked={allRequestedChecked} onChange={toggleAllRequested}
                      disabled={allRequestedCount === 0}
                      className="accent-emerald-600 cursor-pointer disabled:opacity-30" />
                  </th>
                  <th className="text-left px-2 py-2 text-[11px] w-[112px]">발주번호</th>
                  <th className="text-left px-2 py-2 text-[11px] w-[100px]">발주일</th>
                  <th className="text-left px-2 py-2 text-[11px]">업체</th>
                  <th className="text-right px-2 py-2 text-[11px] w-[110px]">합계</th>
                  <th className="text-center px-2 py-2 text-[11px] w-[78px]">상태</th>
                </tr>
              </thead>
              <tbody>
                {orders.length === 0 ? (
                  <tr><td colSpan={6} className="text-center py-12 text-slate-400">{loading ? "조회 중..." : "발주가 없습니다."}</td></tr>
                ) : orders.map((o) => {
                  const checked = selected.has(o.OBJID);
                  const active = o.OBJID === activeId;
                  const lockedByMeRow = !!o.EDITING_BY && o.EDITING_BY === myUserId;
                  const lockedByOtherUser = !!o.EDITING_BY && o.EDITING_BY !== myUserId;
                  return (
                    <tr
                      key={o.OBJID}
                      onClick={() => setActiveId(o.OBJID)}
                      className={`border-t border-slate-100 cursor-pointer ${active ? "bg-emerald-50/60" : lockedByMeRow ? "bg-emerald-50/30 hover:bg-emerald-50/50" : lockedByOtherUser ? "bg-rose-50/30 hover:bg-rose-50/60" : "hover:bg-slate-50"}`}
                    >
                      <td className="px-1.5 py-2 text-center" onClick={(e) => e.stopPropagation()}>
                        <input
                          type="checkbox"
                          checked={checked}
                          onChange={() => toggleOne(o)}
                          disabled={o.STATUS !== "REQUESTED"}
                          className="accent-emerald-600 cursor-pointer disabled:opacity-30 disabled:cursor-not-allowed"
                        />
                      </td>
                      <td className="px-2 py-2 font-semibold text-slate-800 whitespace-nowrap overflow-hidden text-ellipsis text-xs tabular-nums">
                        {lockedByMeRow && <span className="mr-0.5 text-emerald-700">✏️</span>}
                        {lockedByOtherUser && <span className="mr-0.5 text-rose-600">🔒</span>}
                        {o.ORDER_NO}
                      </td>
                      <td className="px-2 py-2 text-slate-600 whitespace-nowrap overflow-hidden text-ellipsis text-xs tabular-nums">{o.ORDER_DATE}</td>
                      <td className="px-2 py-2 font-bold text-sm text-slate-900 overflow-hidden">
                        <div className="truncate">{o.COMPANY_NAME}</div>
                        {lockedByMeRow && <div className="text-[10px] font-bold text-emerald-700 truncate">✏️ 내가 수정 중</div>}
                        {lockedByOtherUser && <div className="text-[10px] font-bold text-rose-600 truncate">🔒 {o.EDITING_BY_NAME}</div>}
                      </td>
                      <td className="px-2 py-2 text-right tabular-nums font-bold whitespace-nowrap overflow-hidden text-xs">₩{fmt(o.TOTAL_AMOUNT)}</td>
                      <td className="px-2 py-2 text-center overflow-hidden">
                        <span className={`inline-block px-1.5 py-0.5 rounded text-[10px] font-semibold border whitespace-nowrap ${STATUS_COLOR[o.STATUS] ?? "bg-slate-100 text-slate-500 border-slate-200"}`}>
                          {STATUS_LABEL[o.STATUS] ?? o.STATUS}
                        </span>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            )}
          </div>
        </div>

        {/* 우측: 거래명세표 미리보기 */}
        <div className="bg-white border border-slate-200 rounded-xl overflow-hidden flex flex-col">
          <div className="px-3 py-2 bg-slate-50 border-b border-slate-200 text-xs font-semibold text-slate-600 flex items-center justify-between">
            <span>거래명세표 미리보기</span>
            {lockedByOther && (
              <span className="inline-flex items-center gap-1 text-[11px] font-bold text-rose-700 bg-rose-100 border border-rose-200 px-2 py-0.5 rounded">
                🔒 {lockedByOther.name} 님이 수정 중
              </span>
            )}
            {lockedByMe && activeId && (
              <span className="inline-flex items-center gap-1 text-[11px] font-bold text-emerald-700 bg-emerald-50 border border-emerald-200 px-2 py-0.5 rounded">
                ✏️ 편집 가능
              </span>
            )}
          </div>
          <div className="flex-1 lg:overflow-auto p-4">
            {!detail ? (
              <div className="h-full flex flex-col items-center justify-center text-slate-400">
                <Package size={48} className="mb-3 opacity-50" />
                <div className="text-sm">왼쪽에서 발주를 선택하세요.</div>
              </div>
            ) : (
              <div className={lockedByOther ? "pointer-events-none opacity-60" : ""}>
                {lockedByOther && (
                  <div className="mb-3 p-3 rounded-lg bg-rose-50 border border-rose-200 text-rose-800 text-sm font-semibold">
                    🔒 <b>{lockedByOther.name}</b> 님이 이 발주를 수정 중입니다. (자동 해제: 마지막 활동 후 2분)
                  </div>
                )}
                <StatementPreview order={detail.order} items={detail.items} supplier={detail.supplier} onCancel={cancelOne} busy={busy} onReload={reloadDetail} onReloadList={load} />
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function StatementPreview({
  order,
  items,
  supplier,
  onCancel,
  busy,
  onReload,
  onReloadList,
}: {
  order: DetailOrder;
  items: DetailLine[];
  supplier: Supplier;
  onCancel: (o: Order) => void;
  busy: boolean;
  onReload: () => void;
  onReloadList: () => void;
}) {
  const [shipping, setShipping] = useState(false);

  const shipNow = async () => {
    // 거래처 미선택 발주는 출고 불가
    if (!order.CUSTOMER_OBJID) {
      Swal.fire({ icon: "warning", title: "거래처를 선택하세요", text: "거래처가 선택되지 않으면 출고할 수 없습니다." });
      return;
    }
    // 품목(ITEM) 라인이 없는 발주는 출고 불가
    if (items.filter((it) => it.KIND === "ITEM").length === 0) {
      Swal.fire({ icon: "warning", title: "품목이 없습니다", text: "품목을 1개 이상 추가해야 출고할 수 있습니다." });
      return;
    }
    const lack = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY));
    if (lack.length > 0) {
      const ok = await Swal.fire({
        icon: "warning",
        title: "재고 부족 항목이 있습니다.",
        html: `<div class="text-left text-sm">${lack.map((it) => `· ${it.ITEM_NAME} (요청 ${fmt(it.QTY)} / 현재고 ${fmt(it.STOCK_QTY)})`).join("<br>")}</div><br>그래도 출고를 시도하시겠습니까?`,
        showCancelButton: true,
        confirmButtonText: "출고 시도",
        cancelButtonText: "취소",
        confirmButtonColor: "#0f766e",
      });
      if (!ok.isConfirmed) return;
    } else {
      const ok = await Swal.fire({
        icon: "question",
        title: "출고 처리하시겠습니까?",
        html: `발주 <b>${order.ORDER_NO}</b><br>거래처: ${order.COMPANY_NAME}<br>합계: ₩${fmt(order.TOTAL_AMOUNT)}<br><br>재고가 차감되고, 거래명세표가 메일로 발송됩니다.`,
        showCancelButton: true,
        confirmButtonText: "출고",
        cancelButtonText: "취소",
        confirmButtonColor: "#0f766e",
      });
      if (!ok.isConfirmed) return;
    }
    setShipping(true);
    try {
      const res = await fetch("/api/m/orders/approve", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ objid: order.OBJID }),
      });
      const j = await res.json();
      if (j.success) {
        await Swal.fire({
          icon: "success",
          title: "출고 완료",
          html: j.mailSent ? "거래명세표 메일이 발송되었습니다." : `<span class="text-rose-600">메일 발송에 실패했습니다.</span>`,
          timer: 1800,
          showConfirmButton: false,
        });
        onReloadList();
        onReload();
      } else {
        Swal.fire({ icon: "error", title: "출고 실패", text: j.message ?? "오류" });
      }
    } finally {
      setShipping(false);
    }
  };

  const statementRef = useRef<HTMLDivElement>(null);
  const lowStock = items.filter((it) => it.KIND === "ITEM" && Number(it.STOCK_QTY) < Number(it.QTY));
  // admin(출고관리) 은 입금완료(PAID) 까지 품목/택배/용차 추가 가능.
  // 계산서발행(INVOICED) / 취소(CANCELLED) 는 수정 불가.
  const editable = order.STATUS === "REQUESTED" || order.STATUS === "APPROVED" || order.STATUS === "PAID";

  // 거래명세표를 이미지로 캡처 → 공유 또는 다운로드
  const handleCapture = async () => {
    if (!statementRef.current) return;
    await captureAndShare({
      node: statementRef.current,
      filename: `${order.ORDER_NO}_거래명세표`,
      shareTitle: `${order.ORDER_NO} 거래명세표`,
      shareText: order.COMPANY_NAME,
      forceWidth: 1100, // 엑셀 가로 출력처럼 펼쳐서 캡처
    });
  };

  // 비고 저장 — 명세표 + 왼쪽 리스트 모두 갱신
  const saveRemark = async (lineObjid: string, remark: string) => {
    const res = await fetch("/api/m/orders/items/remark", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ lineObjid, remark }),
    });
    const j = await res.json();
    if (j.success) { onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "비고 저장 실패", text: j.message });
  };

  // ITEM 라인 수량 즉시 저장 — items/update API
  const saveItemQty = async (lineObjid: string, qty: number) => {
    const res = await fetch("/api/m/orders/items/update", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderObjid: order.OBJID, lines: [{ objid: lineObjid, qty }] }),
    });
    const j = await res.json();
    if (j.success) { onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "수량 저장 실패", text: j.message });
  };

  // ITEM 라인 삭제 — items/update API delete 옵션
  const deleteItemLine = async (lineObjid: string, itemName: string) => {
    const ok = await Swal.fire({
      icon: "question",
      title: `${itemName} 품목을 삭제하시겠습니까?`,
      showCancelButton: true,
      confirmButtonColor: "#dc2626",
      confirmButtonText: "삭제",
    });
    if (!ok.isConfirmed) return;
    const res = await fetch("/api/m/orders/items/update", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderObjid: order.OBJID, lines: [{ objid: lineObjid, delete: true }] }),
    });
    const j = await res.json();
    if (j.success) { onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "삭제 실패", text: j.message });
  };

  const upsertExtra = async (line: { objid?: string; kind: "DELIVERY" | "CHARTER" | "REFUND"; label: string; unitPrice: number; qty: number }) => {
    const res = await fetch("/api/m/orders/lines/save", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderObjid: order.OBJID, lines: [line] }),
    });
    const j = await res.json();
    if (j.success) { onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "저장 실패", text: j.message });
  };
  const deleteExtra = async (objid: string) => {
    const ok = await Swal.fire({ icon: "question", title: "라인을 삭제하시겠습니까?", showCancelButton: true });
    if (!ok.isConfirmed) return;
    const res = await fetch("/api/m/orders/lines/save", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderObjid: order.OBJID, lines: [{ objid, kind: "CHARTER", unitPrice: 0, qty: 1, delete: true }] }),
    });
    const j = await res.json();
    if (j.success) { onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "삭제 실패", text: j.message });
  };
  const addNewExtra = (kind: "DELIVERY" | "CHARTER") => {
    // 같은 종류 라인이 이미 있으면 새로 만들지 않고 수량 +1
    const existing = items.find((it) => it.KIND === kind);
    if (existing) {
      upsertExtra({
        objid: existing.OBJID,
        kind,
        label: existing.EXTRA_LABEL || existing.ITEM_NAME,
        unitPrice: Number(existing.UNIT_PRICE),
        qty: Number(existing.QTY) + 1,
      });
      return;
    }
    const defaults = kind === "DELIVERY"
      ? { unitPrice: 4000, qty: 1, label: "택배비" }
      : { unitPrice: 5000, qty: 1, label: "용차" };
    upsertExtra({ kind, ...defaults });
  };
  // 환불 라인 추가 — admin 전용. 수량 1 고정, 단가는 양수로 받아 내부 음수 저장.
  const addRefund = () => {
    upsertExtra({ kind: "REFUND", label: "환불", unitPrice: 0, qty: 1 });
  };

  // ITEM 라인 추가 (피커 모달)
  const [pickerOpen, setPickerOpen] = useState(false);
  const addItemLines = async (selected: { itemObjid: string; qty: number }[]) => {
    if (selected.length === 0) return;
    const res = await fetch("/api/m/orders/items/add", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderObjid: order.OBJID, items: selected }),
    });
    const j = await res.json();
    if (j.success) { setPickerOpen(false); onReload(); onReloadList(); }
    else Swal.fire({ icon: "error", title: "품목 추가 실패", text: j.message });
  };

  return (
    <div className="text-[14px] text-slate-800 space-y-3">
      {shipping && <Loading message="출고 처리 중..." />}
      {/* 공유/캡처/엑셀/출고 버튼 — 캡처 영역 밖에 배치 */}
      <div className="flex justify-end gap-2 print:hidden flex-wrap">
        <button
          type="button"
          onClick={handleCapture}
          className="inline-flex items-center gap-1 h-8 px-3 rounded-lg bg-amber-100 text-amber-800 text-xs font-bold hover:bg-amber-200"
          title="이미지로 캡처해 카톡 등에 공유"
        >
          📤 이미지 공유
        </button>
        <button
          type="button"
          onClick={() => window.print()}
          className="inline-flex items-center gap-1 h-8 px-3 rounded-lg bg-slate-100 text-slate-700 text-xs font-bold hover:bg-slate-200"
          title="인쇄"
        >
          🖨 인쇄
        </button>
        <a
          href={`/api/m/orders/statement/${order.OBJID}`}
          className="inline-flex items-center gap-1 h-8 px-3 rounded-lg bg-emerald-100 text-emerald-800 text-xs font-bold hover:bg-emerald-200"
          title="거래명세표 엑셀 다운로드"
        >
          <Download size={12} /> 엑셀 다운로드
        </a>
        {order.STATUS === "REQUESTED" && (
          <button
            type="button"
            onClick={shipNow}
            disabled={shipping || busy}
            className="inline-flex items-center gap-1 h-8 px-3 rounded-lg bg-emerald-700 text-white text-xs font-bold hover:bg-emerald-800 disabled:opacity-50 disabled:cursor-not-allowed"
            title="이 발주를 바로 출고 처리"
          >
            <Truck size={12} /> {shipping ? "출고 중..." : "출고"}
          </button>
        )}
      </div>

      <div ref={statementRef} className="bg-white p-3 font-bold [&_*]:font-bold">
      <div className="text-center">
        <h2 className="text-2xl font-bold tracking-[0.3em] text-slate-900">거 래 명 세 표</h2>
      </div>

      {/* 좌: 발주 정보 / 우: 공급자 정보 박스 — 항상 가로 배치 */}
      <div className="flex justify-between items-start gap-3 mt-3 text-[13px]">
        <div className="space-y-0.5 whitespace-nowrap">
          <div><b>발주번호</b> · {order.ORDER_NO}</div>
          <div className="flex items-center gap-1 whitespace-nowrap">
            <b>발주일자</b> ·{" "}
            {editable ? (
              <input
                type="date"
                defaultValue={order.ORDER_DATE}
                onBlur={async (e) => {
                  const v = e.target.value;
                  if (!v || v === order.ORDER_DATE) return;
                  const res = await fetch("/api/m/orders/update-date", {
                    method: "POST", headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ objid: order.OBJID, orderDate: v }),
                  });
                  const j = await res.json();
                  if (j.success) { onReload(); onReloadList(); }
                  else Swal.fire({ icon: "error", title: "발주일 변경 실패", text: j.message });
                }}
                className="h-6 px-1.5 border border-slate-300 rounded text-[13px] bg-white js-no-export"
              />
            ) : (
              order.ORDER_DATE
            )}
            {editable && <span className="text-slate-300 ml-1 js-no-export">(클릭 변경)</span>}
          </div>
          <div><b>현재상태</b> · <span className="font-semibold">{STATUS_LABEL[order.STATUS] ?? order.STATUS}</span></div>
        </div>
        <table className="text-[13px] border border-slate-400 self-start" style={{borderCollapse:'collapse'}}>
          <tbody>
            <tr>
              <td rowSpan={3} className="border border-slate-400 bg-slate-700 text-white text-center font-bold px-2 py-1" style={{writingMode:'vertical-rl',textOrientation:'upright',width:'24px'}}>공급자</td>
              <td className="border border-slate-400 bg-slate-100 text-center font-semibold px-2 py-1 whitespace-nowrap" style={{width:'110px'}}>결제 계좌번호</td>
              <td className="border border-slate-400 px-3 py-1 font-bold text-slate-900 whitespace-nowrap">{supplier.BANK_ACCOUNT}</td>
            </tr>
            <tr>
              <td className="border border-slate-400 bg-slate-100 text-center font-semibold px-2 py-1">전화번호</td>
              <td className="border border-slate-400 px-3 py-1">{supplier.PHONE}</td>
            </tr>
            <tr>
              <td className="border border-slate-400 bg-slate-100 text-center font-semibold px-2 py-1">이메일</td>
              <td className="border border-slate-400 px-3 py-1 text-blue-700">{supplier.EMAIL}</td>
            </tr>
          </tbody>
        </table>
      </div>

      <div className="border border-slate-200 rounded p-2 bg-slate-50/60 mt-3">
        {editable ? (
          <CustomerEditor order={order} onReload={onReload} onReloadList={onReloadList} />
        ) : (
          <>
            <div className="font-semibold text-slate-900">{order.COMPANY_NAME} <span className="text-slate-500 font-normal">귀하</span></div>
            <div className="text-[13px] text-slate-600 mt-0.5 leading-relaxed">
              {order.CEO_NAME && <>대표: {order.CEO_NAME} · </>}
              {order.BIZ_NO && <>사업자번호: {order.BIZ_NO} · </>}
              {order.PHONE && <>전화: {order.PHONE}</>}
              {order.ADDRESS && <div>주소: {order.ADDRESS}</div>}
            </div>
          </>
        )}
      </div>

      {lowStock.length > 0 && (
        <div className="border border-rose-200 bg-rose-50 rounded p-2 text-[13px] text-rose-700 flex items-start gap-2 js-no-export">
          <AlertCircle size={14} className="mt-0.5 flex-shrink-0" />
          <div>
            <b>재고 부족 {lowStock.length}건</b> — 출고 시 거부됩니다:
            <ul className="mt-1 ml-4 list-disc">
              {lowStock.map((it) => (
                <li key={it.SEQ}>{it.ITEM_NAME} (요청 {fmt(it.QTY)} / 현재고 {fmt(it.STOCK_QTY)})</li>
              ))}
            </ul>
          </div>
        </div>
      )}

      {editable && (
        <div className="flex flex-wrap items-center gap-2 js-no-export">
          <span className="text-[13px] text-slate-500">라인 추가:</span>
          <button
            type="button"
            onClick={() => setPickerOpen(true)}
            className="inline-flex items-center gap-1 h-7 px-2.5 rounded bg-emerald-100 text-emerald-800 text-[13px] font-bold hover:bg-emerald-200"
          >
            + 품목 추가
          </button>
          <button
            type="button"
            onClick={() => addNewExtra("DELIVERY")}
            className="inline-flex items-center gap-1 h-7 px-2.5 rounded bg-orange-100 text-orange-800 text-[13px] font-bold hover:bg-orange-200"
          >
            + 택배 추가
          </button>
          <button
            type="button"
            onClick={() => addNewExtra("CHARTER")}
            className="inline-flex items-center gap-1 h-7 px-2.5 rounded bg-sky-100 text-sky-800 text-[13px] font-bold hover:bg-sky-200"
          >
            + 용차 추가
          </button>
          <button
            type="button"
            onClick={addRefund}
            className="inline-flex items-center gap-1 h-7 px-2.5 rounded bg-rose-100 text-rose-700 text-[13px] font-bold hover:bg-rose-200"
            title="환불 라인 추가 — 단가 양수로 입력 시 총액에서 차감됨"
          >
            + 환불 추가
          </button>
        </div>
      )}

      <table className="w-full text-[13px] border border-slate-300 mt-3">
        <thead className="bg-slate-100">
          <tr>
            <th className="border border-slate-300 px-1.5 py-1.5 w-8">#</th>
            <th className="border border-slate-300 px-1.5 py-1.5 text-left">품명</th>
            <th className="border border-slate-300 px-1.5 py-1.5 w-12">구분</th>
            <th className="border border-slate-300 px-1.5 py-1.5 w-14 js-no-export">현재고</th>
            <th className="border border-slate-300 px-1.5 py-1.5 w-14">수량</th>
            <th className="border border-slate-300 px-1.5 py-1.5 w-20">단가</th>
            <th className="border border-slate-300 px-1.5 py-1.5">공급가</th>
            <th className="border border-slate-300 px-1.5 py-1.5">세액</th>
            <th className="border border-slate-300 px-1.5 py-1.5">합계</th>
            <th className="border border-slate-300 px-1.5 py-1.5 w-20">비고</th>
            {editable && <th className="border border-slate-300 px-1 py-1.5 w-8"></th>}
          </tr>
        </thead>
        <tbody className="tabular-nums">
          {items.map((it, idx) => {
            const displaySeq = idx + 1;
            const isExtra = it.KIND === "DELIVERY" || it.KIND === "CHARTER" || it.KIND === "REFUND";
            const lack = !isExtra && Number(it.STOCK_QTY) < Number(it.QTY);
            const kindBadge = it.KIND === "DELIVERY" ? "택배" : it.KIND === "CHARTER" ? "용차" : it.KIND === "REFUND" ? "환불" : null;
            const kindBg = it.KIND === "DELIVERY" ? "bg-orange-50" : it.KIND === "CHARTER" ? "bg-sky-50" : it.KIND === "REFUND" ? "bg-rose-50" : "";

            if (isExtra && editable) {
              return (
                <ExtraRow
                  key={`${it.OBJID}-${it.QTY}-${it.UNIT_PRICE}-${it.EXTRA_LABEL ?? ""}`}
                  line={it}
                  displaySeq={displaySeq}
                  editable={editable}
                  onSave={(updated) => upsertExtra({ objid: it.OBJID, kind: it.KIND as "DELIVERY" | "CHARTER", ...updated })}
                  onDelete={() => deleteExtra(it.OBJID)}
                  onSaveRemark={(r) => saveRemark(it.OBJID, r)}
                />
              );
            }

            return (
              <tr key={it.OBJID || it.SEQ} className={kindBg}>
                <td className="border border-slate-300 px-1.5 py-1 text-center">{displaySeq}</td>
                <td className="border border-slate-300 px-1.5 py-1 whitespace-nowrap">
                  {kindBadge && <span className={`mr-1 text-[11px] font-bold px-1 py-0.5 rounded ${it.KIND === "DELIVERY" ? "bg-orange-200 text-orange-800" : "bg-sky-200 text-sky-800"}`}>{kindBadge}</span>}
                  {it.ITEM_NAME}
                </td>
                <td className={`border border-slate-300 px-1.5 py-1 text-center ${it.IS_TAX_FREE === "Y" ? "text-violet-700" : "text-rose-700"}`}>
                  {it.IS_TAX_FREE === "Y" ? "면세" : "과세"}
                </td>
                <td className={`border border-slate-300 px-1.5 py-1 text-right js-no-export ${lack ? "text-rose-700 font-bold" : "text-slate-600"}`}>
                  {isExtra ? "-" : fmt(it.STOCK_QTY)}
                </td>
                <td className="border border-slate-300 px-1 py-0.5 text-right">
                  {editable
                    ? <QtyInput initial={Number(it.QTY)} onSave={(q) => saveItemQty(it.OBJID, q)} />
                    : fmt(it.QTY)}
                </td>
                <td className="border border-slate-300 px-1.5 py-1 text-right">{fmt(it.UNIT_PRICE)}</td>
                <td className="border border-slate-300 px-1.5 py-1 text-right">{fmt(it.SUPPLY_AMOUNT)}</td>
                <td className="border border-slate-300 px-1.5 py-1 text-right">{it.IS_TAX_FREE === "Y" ? "-" : fmt(it.VAT_AMOUNT)}</td>
                <td className="border border-slate-300 px-1.5 py-1 text-right font-semibold">{fmt(it.TOTAL_AMOUNT)}</td>
                <td className="border border-slate-300 px-1 py-0.5">
                  {editable
                    ? <RemarkInput initial={it.REMARK || ""} onSave={(r) => saveRemark(it.OBJID, r)} />
                    : <span className="text-slate-600 text-[12px]">{it.REMARK || ""}</span>}
                </td>
                {editable && (
                  <td className="border border-slate-300 px-1 py-1 text-center">
                    <button onClick={() => deleteItemLine(it.OBJID, it.ITEM_NAME)} className="text-rose-500 hover:text-rose-700" title="품목 삭제">
                      <X size={12} />
                    </button>
                  </td>
                )}
              </tr>
            );
          })}
          {items.length === 0 && (
            <tr><td colSpan={editable ? 11 : 10} className="border border-slate-300 px-2 py-6 text-center text-slate-400">품목이 없습니다.</td></tr>
          )}
        </tbody>
      </table>

      <table className="ml-auto text-[14px] tabular-nums mt-3">
        <tbody>
          <tr><td className="px-3 py-1 text-violet-700">면세 합계</td><td className="px-3 py-1 text-right min-w-[120px]">₩ {fmt(order.TOTAL_TAXFREE)}</td></tr>
          <tr><td className="px-3 py-1 text-rose-700">과세 공급가</td><td className="px-3 py-1 text-right">₩ {fmt(order.TOTAL_TAXABLE)}</td></tr>
          <tr><td className="px-3 py-1">세액 합계</td><td className="px-3 py-1 text-right">₩ {fmt(order.TOTAL_VAT)}</td></tr>
          <tr className="border-t-2 border-slate-900 font-bold">
            <td className="px-3 py-1.5 whitespace-nowrap text-[13px]">총 합계 (VAT포함)</td>
            <td className="px-3 py-1.5 text-right text-emerald-700 whitespace-nowrap">₩ {fmt(order.TOTAL_AMOUNT)}</td>
          </tr>
        </tbody>
      </table>
      </div>{/* /statementRef capture area */}

      {order.STATUS === "REQUESTED" && (
        <div className="flex items-center justify-between gap-2 pt-3 border-t border-slate-200 flex-wrap">
          <span className="text-[11px] text-slate-400">
            ※ 다중 발주를 일괄 처리하려면 왼쪽 리스트에서 체크박스 선택 후 상단 [출고] 버튼을 사용하세요.
          </span>
          <div className="flex items-center gap-2">
            <button
              onClick={async () => {
                const ok = await Swal.fire({
                  icon: "warning",
                  title: `발주 ${order.ORDER_NO} 삭제`,
                  text: "수기 출고 등 잘못 만든 출고건을 완전히 삭제합니다. (취소 이력 없음)",
                  showCancelButton: true,
                  confirmButtonColor: "#dc2626",
                  confirmButtonText: "삭제",
                });
                if (!ok.isConfirmed) return;
                const res = await fetch("/api/m/orders/delete", {
                  method: "POST", headers: { "Content-Type": "application/json" },
                  body: JSON.stringify({ objid: order.OBJID }),
                });
                const j = await res.json();
                if (j.success) { onReloadList(); }
                else Swal.fire({ icon: "error", title: "삭제 실패", text: j.message });
              }}
              disabled={busy || shipping}
              className="px-3 h-9 rounded-lg border border-rose-300 bg-white text-rose-700 text-xs font-bold hover:bg-rose-50 disabled:opacity-50 inline-flex items-center gap-1"
            >
              <X size={12} /> 삭제
            </button>
            <button
              onClick={() => onCancel(order)}
              disabled={busy || shipping}
              className="px-3 h-9 rounded-lg border border-rose-200 text-rose-700 text-xs font-semibold hover:bg-rose-50 disabled:opacity-50 inline-flex items-center gap-1"
            >
              <X size={12} /> 반려
            </button>
          </div>
        </div>
      )}
      {(order.STATUS === "APPROVED" || order.STATUS === "PAID") && (
        <div className="flex items-center justify-between pt-3 border-t border-slate-200 gap-2 flex-wrap">
          <div className="text-[11px] text-emerald-700 inline-flex items-center gap-1">
            <Check size={12} /> 출고 완료 {order.APPROVE_DATE && `— ${order.APPROVE_DATE}`}
          </div>
          <IssueEinvoiceButton order={order} />
        </div>
      )}

      {pickerOpen && (
        <AdminItemPickerModal
          onClose={() => setPickerOpen(false)}
          onConfirm={addItemLines}
        />
      )}
    </div>
  );
}

function AdminItemPickerModal({ onClose, onConfirm }: {
  onClose: () => void;
  onConfirm: (selected: { itemObjid: string; qty: number }[]) => void;
}) {
  const [items, setItems] = useState<Array<{ OBJID: string; ITEM_CODE: string; ITEM_NAME: string; UNIT_PRICE: number; STOCK_QTY: number; IS_TAX_FREE: string; UNIT: string }>>([]);
  const [keyword, setKeyword] = useState("");
  const [cart, setCart] = useState<Record<string, number>>({});

  useEffect(() => {
    fetch("/api/m/items/list", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ stockFilter: "AVAILABLE" }),
    })
      .then((r) => r.json())
      .then((j) => setItems(j.RESULTLIST ?? []))
      .catch(() => {});
  }, []);

  const filtered = useMemo(() => {
    const kw = keyword.trim().toLowerCase();
    if (!kw) return items;
    return items.filter((it) => it.ITEM_NAME?.toLowerCase().includes(kw) || it.ITEM_CODE?.toLowerCase().includes(kw));
  }, [items, keyword]);

  const confirm = () => {
    const selected = Object.entries(cart)
      .filter(([, q]) => q > 0)
      .map(([itemObjid, qty]) => ({ itemObjid, qty }));
    onConfirm(selected);
  };

  return (
    <div className="fixed inset-0 bg-slate-900/60 z-[60] flex items-center justify-center p-3" onClick={onClose}>
      <div className="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[85vh] overflow-hidden flex flex-col" onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-4 py-3 border-b border-slate-200">
          <h3 className="font-bold">품목 추가</h3>
          <button onClick={onClose} className="text-slate-400 hover:text-slate-700"><X size={18} /></button>
        </div>
        <div className="px-4 py-2 border-b border-slate-200">
          <input value={keyword} onChange={(e) => setKeyword(e.target.value)}
            placeholder="품목명/코드 검색"
            className="w-full h-9 px-3 rounded border border-slate-300 text-sm" />
        </div>
        <div className="flex-1 overflow-y-auto">
          <table className="w-full text-xs">
            <thead className="bg-slate-50 text-slate-600 sticky top-0">
              <tr>
                <th className="text-left p-2">품목</th>
                <th className="text-right p-2 w-16">재고</th>
                <th className="text-right p-2 w-20">단가</th>
                <th className="text-center p-2 w-20">수량</th>
              </tr>
            </thead>
            <tbody>
              {filtered.length === 0 ? (
                <tr><td colSpan={4} className="text-center py-8 text-slate-400">검색 결과 없음</td></tr>
              ) : filtered.slice(0, 100).map((it) => (
                <tr key={it.OBJID} className="border-t border-slate-100 hover:bg-slate-50">
                  <td className="p-2">
                    <div className="font-semibold">{it.ITEM_NAME}</div>
                    <div className="text-[10px] text-slate-400">{it.ITEM_CODE} · {it.IS_TAX_FREE === "Y" ? "면세" : "과세"}</div>
                  </td>
                  <td className="p-2 text-right tabular-nums">{Number(it.STOCK_QTY).toLocaleString()}</td>
                  <td className="p-2 text-right tabular-nums">{Number(it.UNIT_PRICE).toLocaleString()}</td>
                  <td className="p-2 text-center">
                    <input type="number" min={0} max={Number(it.STOCK_QTY)}
                      value={cart[it.OBJID] ?? 0}
                      onChange={(e) => {
                        const v = Math.min(Number(it.STOCK_QTY), Math.max(0, Number(e.target.value) || 0));
                        setCart((p) => ({ ...p, [it.OBJID]: v }));
                      }}
                      className="w-16 h-7 px-1 border border-slate-200 rounded text-right tabular-nums" />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <div className="flex justify-end gap-2 px-4 py-3 border-t border-slate-200">
          <button onClick={onClose} className="h-9 px-4 rounded border border-slate-200 text-sm font-semibold">취소</button>
          <button onClick={confirm}
            disabled={Object.values(cart).every((q) => !q)}
            className="h-9 px-5 rounded bg-emerald-700 text-white text-sm font-bold hover:bg-emerald-800 disabled:opacity-40">
            추가
          </button>
        </div>
      </div>
    </div>
  );
}

function ExtraRow({ line, displaySeq, editable, onSave, onDelete, onSaveRemark }: {
  line: DetailLine;
  displaySeq: number;
  editable: boolean;
  onSave: (data: { label: string; unitPrice: number; qty: number }) => void;
  onDelete: () => void;
  onSaveRemark: (r: string) => void;
}) {
  const isRefund = line.KIND === "REFUND";
  const isDelivery = line.KIND === "DELIVERY";
  // 환불 라인은 DB에 음수로 저장됨 — UI 에선 양수로 표시/입력 후 저장 시 부호는 API 가 처리.
  const initialUnit = isRefund ? Math.abs(Number(line.UNIT_PRICE) || 0) : (Number(line.UNIT_PRICE) || 0);
  const [label, setLabel] = useState(line.EXTRA_LABEL || line.ITEM_NAME);
  const [unitPrice, setUnitPrice] = useState(initialUnit);
  const [qty, setQty] = useState(isRefund ? 1 : (Number(line.QTY) || 1));

  useEffect(() => {
    setLabel(line.EXTRA_LABEL || line.ITEM_NAME);
    setUnitPrice(isRefund ? Math.abs(Number(line.UNIT_PRICE) || 0) : (Number(line.UNIT_PRICE) || 0));
    setQty(isRefund ? 1 : (Number(line.QTY) || 1));
  }, [line.OBJID, line.EXTRA_LABEL, line.ITEM_NAME, line.UNIT_PRICE, line.QTY, isRefund]);

  // 표시용 금액 — 환불은 음수
  const displayTotal = isRefund ? -unitPrice * qty : Math.round(unitPrice * qty);
  const displaySupply = isRefund ? displayTotal : Math.round(displayTotal / 1.1);
  const displayVat = isRefund ? 0 : displayTotal - displaySupply;

  const bg = isRefund ? "bg-rose-50" : isDelivery ? "bg-orange-50" : "bg-sky-50";
  const badgeCls = isRefund ? "bg-rose-200 text-rose-800" : isDelivery ? "bg-orange-200 text-orange-800" : "bg-sky-200 text-sky-800";
  const badgeText = isRefund ? "환불" : isDelivery ? "택배" : "용차";

  const commit = () => {
    const originalUnit = isRefund ? Math.abs(Number(line.UNIT_PRICE) || 0) : Number(line.UNIT_PRICE);
    const dirty = label !== (line.EXTRA_LABEL || line.ITEM_NAME)
               || unitPrice !== originalUnit
               || qty !== Number(line.QTY);
    if (dirty && qty > 0 && unitPrice >= 0) onSave({ label, unitPrice, qty });
  };

  return (
    <tr className={bg}>
      <td className="border border-slate-300 px-1.5 py-1 text-center">{displaySeq}</td>
      <td className="border border-slate-300 px-1.5 py-1">
        <span className={`mr-1 text-[9px] font-bold px-1 py-0.5 rounded ${badgeCls}`}>{badgeText}</span>
        <input
          value={label}
          onChange={(e) => setLabel(e.target.value)}
          onBlur={commit}
          onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); }}
          className="h-6 px-1.5 border border-slate-200 rounded text-[11px] bg-white w-[calc(100%-50px)] inline"
        />
      </td>
      <td className={`border border-slate-300 px-1.5 py-1 text-center ${isRefund ? "text-violet-700" : "text-rose-700"}`}>
        {isRefund ? "면세" : "과세"}
      </td>
      <td className="border border-slate-300 px-1.5 py-1 text-right text-slate-400 js-no-export">-</td>
      <td className="border border-slate-300 px-1 py-1 text-right">
        {isRefund ? (
          <span className="text-slate-500">1</span>
        ) : (
          <input type="number" min={1} value={qty}
            onChange={(e) => setQty(Number(e.target.value))}
            onBlur={commit}
            onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); }}
            className="w-full h-6 px-1 border border-slate-200 rounded text-[11px] text-right tabular-nums bg-white" />
        )}
      </td>
      <td className="border border-slate-300 px-1 py-1 text-right">
        <input type="number" min={0} value={unitPrice}
          onChange={(e) => setUnitPrice(Number(e.target.value))}
          onBlur={commit}
          onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); }}
          className={`w-full h-6 px-1 border border-slate-200 rounded text-[11px] text-right tabular-nums bg-white ${isRefund ? "text-rose-700 font-bold" : ""}`} />
      </td>
      <td className={`border border-slate-300 px-1.5 py-1 text-right tabular-nums ${isRefund ? "text-rose-700 font-bold" : ""}`}>{Number(displaySupply).toLocaleString("ko-KR")}</td>
      <td className="border border-slate-300 px-1.5 py-1 text-right tabular-nums">{isRefund ? "-" : Number(displayVat).toLocaleString("ko-KR")}</td>
      <td className={`border border-slate-300 px-1.5 py-1 text-right tabular-nums font-semibold ${isRefund ? "text-rose-700" : ""}`}>{Number(displayTotal).toLocaleString("ko-KR")}</td>
      <td className="border border-slate-300 px-1 py-0.5">
        {editable
          ? <RemarkInput initial={line.REMARK || ""} onSave={onSaveRemark} />
          : <span className="text-slate-600 text-[10px]">{line.REMARK || ""}</span>}
      </td>
      <td className="border border-slate-300 px-1 py-1 text-center">
        <button type="button" onClick={onDelete} className="text-rose-500 hover:text-rose-700" title="삭제">
          <X size={12} />
        </button>
      </td>
    </tr>
  );
}

function IssueEinvoiceButton({ order }: { order: DetailOrder }) {
  const [busy, setBusy] = useState(false);
  const [issued, setIssued] = useState<{ ntsInvoiceNo?: string; provider?: string } | null>(null);

  const onIssue = async (kind: "TAX" | "TAXFREE") => {
    const ok = await Swal.fire({
      icon: "question",
      title: `${kind === "TAX" ? "세금계산서" : "계산서(면세)"} 발행`,
      html: `<div class="text-sm">발주 <b>${order.ORDER_NO}</b><br>합계 ₩${fmt(order.TOTAL_AMOUNT)}<br><br>전자세금계산서를 발행하시겠습니까?</div>`,
      showCancelButton: true,
      confirmButtonText: "발행",
      cancelButtonText: "취소",
      confirmButtonColor: "#0f766e",
    });
    if (!ok.isConfirmed) return;
    setBusy(true);
    try {
      const res = await fetch("/api/m/einvoices/issue", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ orderObjid: order.OBJID, invoiceKind: kind, invoiceType: "NORMAL" }),
      });
      const j = await res.json();
      if (j.success) {
        setIssued({ ntsInvoiceNo: j.ntsInvoiceNo, provider: j.provider });
        await Swal.fire({
          icon: "success",
          title: "발행 완료",
          html: `
            <div class="text-sm">
              <div>승인번호: <b>${j.ntsInvoiceNo ?? "-"}</b></div>
              <div>처리방식: <b>${j.provider}</b></div>
              ${j.message ? `<div class="text-xs text-slate-500 mt-2">${j.message}</div>` : ""}
            </div>`,
        });
      } else {
        Swal.fire({ icon: "error", title: "발행 실패", text: j.message ?? "오류" });
      }
    } finally { setBusy(false); }
  };

  if (issued) {
    return (
      <span className="text-[11px] text-emerald-700 inline-flex items-center gap-1.5">
        <Check size={11} /> 세금계산서 발행됨
        {issued.ntsInvoiceNo && <span className="font-mono text-slate-500">({issued.ntsInvoiceNo})</span>}
      </span>
    );
  }
  return (
    <div className="flex gap-1.5">
      <button
        type="button"
        disabled={busy}
        onClick={() => onIssue("TAXFREE")}
        className="inline-flex items-center gap-1 h-8 px-2.5 rounded bg-violet-600 text-white text-[11px] font-bold hover:bg-violet-700 disabled:opacity-50"
        title="면세 계산서 발행"
      >
        계산서(면세)
      </button>
    </div>
  );
}

function RemarkInput({ initial, onSave }: { initial: string; onSave: (r: string) => void }) {
  const [val, setVal] = useState(initial);
  useEffect(() => { setVal(initial); }, [initial]);
  const dirty = val !== initial;
  return (
    <div className="flex items-center gap-1">
      <input
        value={val}
        onChange={(e) => setVal(e.target.value)}
        onBlur={() => { if (dirty) onSave(val); }}
        onKeyDown={(e) => { if (e.key === "Enter") { (e.target as HTMLInputElement).blur(); } }}
        placeholder="비고"
        className="w-full h-6 px-1.5 border border-slate-200 rounded text-[10px] bg-white"
      />
    </div>
  );
}

// ITEM 라인 수량 인라인 인풋 — onBlur / Enter 시 자동 저장
function QtyInput({ initial, onSave }: { initial: number; onSave: (q: number) => void }) {
  const [val, setVal] = useState(String(initial));
  useEffect(() => { setVal(String(initial)); }, [initial]);
  const commit = () => {
    const n = Number(val);
    if (!Number.isFinite(n) || n <= 0) { setVal(String(initial)); return; }
    if (n === initial) return;
    onSave(n);
  };
  return (
    <input
      type="number" min={1} value={val}
      onChange={(e) => setVal(e.target.value)}
      onBlur={commit}
      onKeyDown={(e) => { if (e.key === "Enter") (e.target as HTMLInputElement).blur(); }}
      className="w-11 h-6 px-1 border border-slate-200 rounded text-[11px] text-right tabular-nums bg-white"
    />
  );
}

// 수기 발주 작성 — admin 이 전화 요청 등을 받아 거래처 대신 발주 등록
// 거래처 변경 — admin 이 출고관리 detail 안에서 거래처 직접 select
function CustomerEditor({ order, onReload, onReloadList }: {
  order: DetailOrder; onReload: () => void; onReloadList: () => void;
}) {
  const [customers, setCustomers] = useState<{ USER_ID: string; USER_NAME: string }[]>([]);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    fetch("/api/m/customers/list", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" })
      .then((r) => r.json())
      .then((j) => setCustomers(j.RESULTLIST ?? []))
      .catch(() => {});
  }, []);

  const change = async (newId: string) => {
    if (!newId || newId === order.CUSTOMER_OBJID) return;
    setBusy(true);
    try {
      const res = await fetch("/api/m/orders/update-customer", {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ objid: order.OBJID, customerObjid: newId }),
      });
      const j = await res.json();
      if (j.success) { onReload(); onReloadList(); }
      else Swal.fire({ icon: "error", title: "거래처 변경 실패", text: j.message });
    } finally { setBusy(false); }
  };

  return (
    <div className="flex items-center gap-2 flex-wrap">
      <span className="text-[11px] text-slate-500 font-semibold">거래처</span>
      <div className="min-w-[260px]">
        <SearchableSelect
          options={customers.map((c) => ({ value: c.USER_ID, label: `${c.USER_NAME} (${c.USER_ID})` }))}
          value={order.CUSTOMER_OBJID ?? ""}
          onChange={(v) => change(v)}
          placeholder="거래처 검색"
          disabled={busy}
        />
      </div>
      <span className="text-slate-500 font-normal">귀하</span>
      {order.COMPANY_NAME && (
        <span className="text-[10px] text-slate-400 ml-1">
          {order.CEO_NAME && <>대표 {order.CEO_NAME} · </>}
          {order.PHONE && <>{order.PHONE}</>}
        </span>
      )}
    </div>
  );
}

// 수기 발주 — 클릭하면 빈 발주 즉시 생성 → 자동 활성화. 거래처/품목은 detail 에서 채움.
function ManualOrderButton({ onCreated }: { onCreated: (newObjid: string) => void }) {
  const [busy, setBusy] = useState(false);
  const onClick = async () => {
    setBusy(true);
    try {
      const res = await fetch("/api/m/orders/create-empty", { method: "POST" });
      const j = await res.json();
      if (j.success) onCreated(j.objId);
      else Swal.fire({ icon: "error", title: "생성 실패", text: j.message });
    } finally { setBusy(false); }
  };
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={busy}
      className="h-9 px-3 rounded-lg bg-white border border-amber-300 text-amber-700 text-sm font-bold hover:bg-amber-50 disabled:opacity-50 inline-flex items-center gap-1.5"
      title="빈 출고건을 생성하여 오른쪽에서 거래처/품목 채워가는 흐름"
    >
      <PhoneCall size={14} /> 수기 출고
    </button>
  );
}
