// Reservation multi-step flow

// Auth gate — redirects to /login if user is not signed in.
// Stores the current path so login.html can return the user here after sign-in.
function useReserveAuthGate(navigate) {
  const [user, setUser] = React.useState(null);
  const [authReady, setAuthReady] = React.useState(false);

  React.useEffect(() => {
    let unsub = null;
    let cancelled = false;
    const start = () => {
      if (cancelled) return;
      if (!window.firebaseAuth || !window.firebaseOnAuthStateChanged) {
        setTimeout(start, 50);
        return;
      }
      unsub = window.firebaseOnAuthStateChanged(window.firebaseAuth, (u) => {
        setUser(u);
        setAuthReady(true);
      });
    };
    start();
    return () => { cancelled = true; if (unsub) unsub(); };
  }, []);

  React.useEffect(() => {
    if (authReady && !user) {
      try {
        sessionStorage.setItem("postLoginRedirect", window.location.pathname);
      } catch (_) {}
      navigate("login");
    }
  }, [authReady, user]);

  return { user, authReady };
}

function AuthGateLoading() {
  return (
    <main style={{ minHeight: "60vh", display: "grid", placeItems: "center" }}>
      <div style={{ color: "var(--muted)", fontSize: 14 }}>로그인 확인 중…</div>
    </main>
  );
}

// Daum 우편번호 팝업 — 결과를 콜백으로 전달.
//   onSelect({ zip, base }) — zip: 5자리 새우편번호, base: 도로명 주소
function openPostcodePopup(onSelect) {
  if (typeof window.daum === "undefined" || !window.daum.Postcode) {
    alert("우편번호 서비스를 불러오지 못했습니다. 잠시 후 다시 시도해주세요.");
    return;
  }
  new window.daum.Postcode({
    oncomplete: (data) => {
      const base = data.roadAddress || data.jibunAddress || data.address || "";
      onSelect({ zip: data.zonecode || "", base });
    },
  }).open();
}

const STEPS_HOME = [
  { key: "from", label: "보내는 곳" },
  { key: "to", label: "받는 곳" },
  { key: "item", label: "물품 정보" },
  { key: "schedule", label: "픽업 일정" },
  { key: "pay", label: "결제 · 확인" },
];
const STEPS_STORE = [
  { key: "from", label: "보내는 분" },
  { key: "to", label: "받는 곳" },
  { key: "item", label: "물품 정보" },
  { key: "store", label: "접수 편의점" },
  { key: "pay", label: "결제 · 확인" },
];

const CONV_BRANDS = [
  { id: "gs25", name: "GS25", short: "GS", color: "#0091EA", keyword: "GS25" },
  { id: "cu", name: "CU", short: "CU", color: "#5B2C81", keyword: "CU 편의점" },
  { id: "seven", name: "세븐일레븐", short: "7", color: "#EE3124", keyword: "세븐일레븐" },
  { id: "emart24", name: "이마트24", short: "e24", color: "#FFB81C", keyword: "이마트24" },
];

// 카카오 지도 SDK 가 로드되면 resolve. 여러 번 호출해도 한 번만 로드.
let _kakaoReadyPromise = null;
function ensureKakaoMapsReady() {
  if (_kakaoReadyPromise) return _kakaoReadyPromise;
  _kakaoReadyPromise = new Promise((resolve, reject) => {
    const start = () => {
      if (window.kakao && window.kakao.maps) {
        window.kakao.maps.load(() => resolve(window.kakao));
      } else {
        setTimeout(start, 50);
      }
    };
    start();
    setTimeout(() => reject(new Error("Kakao Maps SDK 로드 실패")), 10000);
  });
  return _kakaoReadyPromise;
}

function fmtDistance(meters) {
  const m = Number(meters);
  if (!Number.isFinite(m)) return "";
  return m < 1000 ? `${Math.round(m)}m` : `${(m / 1000).toFixed(1)}km`;
}

// 박스 가격 — 서버 SHIPPING_PRICES / CONVENIENCE_PRICES 와 일치.
// 편의점 기본은 CU/EMART/7Eleven 가격. GS25 L=5000 (StoreBrand 변경 시 동적 처리).
function getBoxSizes(method, cvsBrand) {
  if (method === "store") {
    const lPrice = cvsBrand === "gs25" ? 5000 : 4500;
    return [
      { id: "S", title: "소형 (S)", sub: "500g / 80cm 이하", price: 3500 },
      { id: "M", title: "중형 (M)", sub: "5kg / 80cm 이하", price: 4000 },
      { id: "L", title: "대형 (L)", sub: "20kg / 140cm 이하", price: lPrice },
    ];
  }
  // 방문(한진): SML → A/B/E 박스타입
  return [
    { id: "S", title: "소형 (S)", sub: "5kg / 60cm 이하", price: 4500 },
    { id: "M", title: "중형 (M)", sub: "10kg / 80cm 이하", price: 5500 },
    { id: "L", title: "대형 (L)", sub: "20kg / 140cm 이하", price: 8500 },
  ];
}

// 편의점 브랜드 id → 서버 cvsBrand 코드
const CVS_BRAND_CODE = {
  gs25: "GS25",
  cu: "CU",
  seven: "7Eleven",
  emart24: "Emart24",
};

// 방문(visit)용 SML → 한진 박스타입 매핑
const VISIT_BOX_TYPE = { S: "A", M: "B", L: "E" };

// 한국 휴대폰 — 010 시작 + 정확히 11자리
function isValidKrMobile(v) {
  return /^010\d{8}$/.test(String(v || ""));
}

// UI state → createPaymentSession 페이로드
function buildReservationData(d, method) {
  const memo = (d.memo || "").trim();
  const priceNum = Number(String(d.itemPrice || "").replace(/[^0-9]/g, "")) || 0;

  const common = {
    sndrNm: d.fromName.trim(),
    sndrMobileNo: d.fromPhone.trim(),
    sndrBaseAddr: d.fromAddress.trim(),
    sndrDtlAddr: d.fromDetail.trim(),
    sndrZip: d.fromZip.trim(),
    rcvrNm: d.toName.trim(),
    rcvrMobileNo: d.toPhone.trim(),
    rcvrBaseAddr: d.toAddress.trim(),
    rcvrDtlAddr: d.toDetail.trim(),
    rcvrZip: d.toZip.trim(),
    comodityNm: d.comodityNm.trim(),
    item: { price: priceNum },
    rcvrAskCntent: memo,
    isJeju: false,
    remoteAreaCategory: "none",
  };

  if (method === "store") {
    const sml = d.boxSize; // S/M/L
    return {
      ...common,
      type: "convenience",
      cvsBrand: CVS_BRAND_CODE[d.storeBrand] || "CU",
      boxes: {
        S: sml === "S" ? 1 : 0,
        M: sml === "M" ? 1 : 0,
        L: sml === "L" ? 1 : 0,
      },
    };
  }

  // 방문택배 — SML → A/B/E 변환
  const hjType = VISIT_BOX_TYPE[d.boxSize] || "B";
  return {
    ...common,
    type: "visit",
    boxes: {
      S: 0,
      A: hjType === "A" ? 1 : 0,
      B: hjType === "B" ? 1 : 0,
      C: 0,
      E: hjType === "E" ? 1 : 0,
    },
    visitDate: d.pickupDate, // YYYY-MM-DD
  };
}

const PICKUP_TIMES = [
  "09:00 - 11:00", "11:00 - 13:00", "13:00 - 15:00", "15:00 - 17:00",
  "17:00 - 19:00", "19:00 - 21:00"
];

const DAYS = ["일", "월", "화", "수", "목", "금", "토"];

// 2026 한국 공휴일 — Flutter _fallbackHolidays 와 동기화 (visit_courier_reservation_screen.dart:195).
// 매년 새해 전 갱신 필요.
const PICKUP_HOLIDAYS_2026 = new Set([
  "2026-01-01", // 신정
  "2026-02-16", // 설날 연휴
  "2026-02-17", // 설날
  "2026-02-18", // 설날 연휴
  "2026-03-01", // 삼일절 (일요일)
  "2026-03-02", // 삼일절 대체공휴일
  "2026-05-05", // 어린이날
  "2026-05-24", // 부처님오신날 (일요일)
  "2026-05-25", // 부처님오신날 대체공휴일
  "2026-06-06", // 현충일
  "2026-08-15", // 광복절
  "2026-09-24", // 추석 연휴
  "2026-09-25", // 추석
  "2026-09-26", // 추석 연휴
  "2026-10-03", // 개천절
  "2026-10-09", // 한글날
  "2026-12-25", // 성탄절
]);

function toKstDateKey(d) {
  // KST 기준 YYYY-MM-DD (UTC 변환 시 날짜 밀림 방지)
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

// 오늘부터 평일(일요일/공휴일 제외) 픽업 가능일 count 개 반환
function buildDates(count = 7) {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const todayKey = toKstDateKey(today);
  const tomorrow = new Date(today);
  tomorrow.setDate(tomorrow.getDate() + 1);
  const tomorrowKey = toKstDateKey(tomorrow);

  const out = [];
  for (let i = 0; out.length < count && i < 60; i++) {
    const d = new Date(today);
    d.setDate(today.getDate() + i);
    if (d.getDay() === 0) continue; // 일요일 제외
    const key = toKstDateKey(d);
    if (PICKUP_HOLIDAYS_2026.has(key)) continue; // 공휴일 제외
    out.push({
      key,
      day: DAYS[d.getDay()],
      num: d.getDate(),
      isToday: key === todayKey,
      isTomorrow: key === tomorrowKey,
    });
  }
  return out;
}

const Reservation = ({ navigate, method: initialMethod }) => {
  const { user, authReady } = useReserveAuthGate(navigate);
  const method = initialMethod || 'home';
  const [step, setStep] = React.useState(0);
  const [done, setDone] = React.useState(false);
  const [data, setData] = React.useState({
    fromName: "",
    fromPhone: "",
    fromAddress: "",
    fromDetail: "",
    fromZip: "",
    toName: "",
    toPhone: "",
    toAddress: "",
    toDetail: "",
    toZip: "",
    boxSize: "M",
    comodityNm: "",
    itemPrice: "",
    pickupDate: null,
    pickupTime: "",
    storeBrand: "gs25",
    store: null, // { id, name, addr, dist }
    memo: "",
    payment: "card",
    agree: false,
  });
  const [submitting, setSubmitting] = React.useState(false);
  const [submitError, setSubmitError] = React.useState("");

  const dates = React.useMemo(() => buildDates(7), []);
  React.useEffect(() => {
    if (!data.pickupDate) setData(d => ({ ...d, pickupDate: dates[1].key }));
  }, []);

  const update = (k, v) => setData(d => ({ ...d, [k]: v }));

  const STEPS = method === "store" ? STEPS_STORE : STEPS_HOME;
  const boxSizes = React.useMemo(
    () => getBoxSizes(method, data.storeBrand),
    [method, data.storeBrand]
  );
  const selectedBox = boxSizes.find(b => b.id === data.boxSize) || boxSizes[1];
  const total = selectedBox.price;

  const canNext = () => {
    if (step === 0) {
      return data.fromName.trim()
        && isValidKrMobile(data.fromPhone)
        && (method === "store" || (data.fromAddress && data.fromZip));
    }
    if (step === 1) {
      return data.toName.trim()
        && isValidKrMobile(data.toPhone)
        && data.toAddress && data.toZip;
    }
    if (step === 2) {
      return data.boxSize
        && data.comodityNm.trim()
        && Number(data.itemPrice) > 0;
    }
    if (step === 3) {
      return method === "store" ? !!data.store : (data.pickupDate && data.pickupTime);
    }
    if (step === 4) return data.agree && !submitting;
    return false;
  };

  // 최종 단계: createPaymentSession 호출 → paymentUrl 이동
  const submitReservation = async () => {
    setSubmitError("");
    setSubmitting(true);
    try {
      if (!window.firebaseFunctions || !window.firebaseHttpsCallable) {
        throw new Error("결제 서비스 초기화 중입니다. 잠시 후 다시 시도해주세요.");
      }
      const callable = window.firebaseHttpsCallable(
        window.firebaseFunctions,
        "createPaymentSession"
      );
      const reservationData = buildReservationData(data, method);
      const { data: res } = await callable({ reservationData });
      const paymentUrl = res?.paymentUrl;
      if (!paymentUrl) throw new Error("결제 세션 생성에 실패했습니다.");
      // 웹 클라이언트임을 success/fail 페이지가 인식할 수 있도록 표시
      const sep = paymentUrl.includes("?") ? "&" : "?";
      window.location.href = `${paymentUrl}${sep}client=web`;
    } catch (e) {
      console.error("[reserve] createPaymentSession failed", e);
      setSubmitError(e?.message || "결제 세션 생성 중 오류가 발생했습니다.");
      setSubmitting(false);
    }
  };

  const next = () => {
    if (step < STEPS.length - 1) {
      setStep(s => s + 1);
    } else {
      submitReservation();
    }
  };
  const back = () => {
    if (step > 0) setStep(s => s - 1);
  };

  if (!authReady || !user) return <AuthGateLoading />;
  if (done) return <DoneScreen data={data} method={method} total={total} navigate={navigate} />;

  // ─────────── Main wizard ───────────
  return (
    <div className="reserve-shell">
      <aside className="reserve-sidebar">
        <h2>택배 예약</h2>
        <p>
          {method === "store"
            ? "가까운 편의점에 맡기고 끝. 24시간 접수 가능해요."
            : "3단계로 끝나는 원클릭 예약. 다음 번엔 자동 채워드려요."}
        </p>
        <div style={{
          display: "inline-flex", alignItems: "center", gap: 8,
          padding: "6px 12px", background: "var(--primary-50)", color: "var(--primary)",
          borderRadius: 999, fontSize: 12, fontWeight: 600, marginBottom: 20
        }}>
          <Icon name={method === "store" ? "warehouse" : "truck"} size={13}/>
          {method === "store" ? "편의점 택배" : "방문 택배"}
          <a href="#" onClick={e => { e.preventDefault(); navigate('reserve'); }}
            style={{ color: "var(--muted)", marginLeft: 4, fontWeight: 500, textDecoration: "underline" }}>
            변경
          </a>
        </div>
        <ol className="step-list">
          {STEPS.map((s, i) => {
            const cls = i < step ? "done" : i === step ? "active" : "";
            return (
              <li key={s.key} className={cls}>
                <span className="step-num">
                  {i < step ? <Icon name="check-bold" size={11}/> : i + 1}
                </span>
                {s.label}
              </li>
            );
          })}
        </ol>
      </aside>

      <section className="reserve-main">
        <div className="reserve-step-head">
          <div className="eyebrow">STEP {step + 1} / {STEPS.length}</div>
          <h3>{STEPS[step].label}</h3>
          <p>{stepDescription(step, method)}</p>
        </div>

        {step === 0 && <FromForm data={data} update={update} method={method} />}
        {step === 1 && <ToForm data={data} update={update} />}
        {step === 2 && <ItemForm data={data} update={update} boxSizes={boxSizes} />}
        {step === 3 && method === "store" && (
          <StoreForm data={data} update={update} />
        )}
        {step === 3 && method !== "store" && (
          <ScheduleForm data={data} update={update} dates={dates} />
        )}
        {step === 4 && <ConfirmStep data={data} update={update} method={method} total={total} selectedBox={selectedBox} />}

        {submitError && (
          <div style={{
            marginTop: 12, padding: "10px 14px", borderRadius: 8,
            background: "rgba(220, 38, 38, 0.08)", color: "var(--danger)", fontSize: 13
          }}>
            {submitError}
          </div>
        )}

        <div className="step-actions">
          {step > 0 && (
            <button className="btn btn-ghost" onClick={back} disabled={submitting}>
              <Icon name="arrow-left" size={14}/> 이전
            </button>
          )}
          <button
            className="btn btn-primary"
            disabled={!canNext()}
            onClick={next}
            style={{ opacity: canNext() ? 1 : 0.5, marginLeft: "auto" }}
          >
            {step === STEPS.length - 1
              ? (submitting ? "결제 페이지로 이동 중…" : "결제 진행")
              : "다음"}
            <Icon name="arrow-right" size={14}/>
          </button>
        </div>
      </section>
    </div>
  );
};

// ─────────── Method picker (standalone page) ───────────
const ReserveMethodPicker = ({ navigate }) => {
  const { user, authReady } = useReserveAuthGate(navigate);
  if (!authReady || !user) return <AuthGateLoading />;
  return (
    <main>
      <MethodPicker
        onPick={(m) => navigate(m === 'store' ? 'reserve-store' : 'reserve-home')}
        onCancel={() => navigate('home')}
      />
    </main>
  );
};

// ─────────── Method picker (entry screen) ───────────
const MethodPicker = ({ onPick, onCancel }) => {
  return (
    <div className="method-picker">
      <div className="method-picker-head">
        <div className="eyebrow">택배 예약</div>
        <h2>어떻게 보내시겠어요?</h2>
        <p>두 가지 방식 중 편한 쪽으로 골라주세요. 언제든지 바꿀 수 있어요.</p>
      </div>

      <div className="method-cards">
        <button className="method-card" type="button" onClick={() => onPick('home')}>
          <span className="recommended">추천</span>
          <div className="illu"><Icon name="truck" size={28}/></div>
          <h3>방문 택배</h3>
          <p>기사님이 직접 픽업하러 방문합니다. 무거운 짐도, 큰 박스도 그대로 두면 끝.</p>
          <ul>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>집·사무실 어디든 픽업</li>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>대형(L)까지 모든 사이즈 가능</li>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>당일 픽업 가능 (지역에 따라)</li>
          </ul>
          <div className="meta">
            <span className="lbl">중형(M) 기준</span>
            <span className="val"><em>4,800</em>원~</span>
          </div>
        </button>

        <button className="method-card" type="button" onClick={() => onPick('store')}>
          <div className="illu"><Icon name="warehouse" size={28}/></div>
          <h3>편의점 택배</h3>
          <p>가까운 GS25, CU, 세븐일레븐, 이마트24에 직접 맡기고 가세요. 24시간 접수돼요.</p>
          <ul>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>24시간 언제든 접수 가능</li>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>방문 시간 약속 불필요</li>
            <li><span className="pc"><Icon name="check-bold" size={9}/></span>500원 더 저렴해요</li>
          </ul>
          <div className="meta">
            <span className="lbl">중형(M) 기준</span>
            <span className="val"><em>4,300</em>원~</span>
          </div>
        </button>
      </div>

      <div className="method-foot">
        <button className="btn btn-ghost" onClick={onCancel}>
          <Icon name="arrow-left" size={14}/> 홈으로
        </button>
      </div>
    </div>
  );
};

const stepDescription = (step, method) => {
  const isStore = method === "store";
  return [
    isStore
      ? "택배를 보내는 분의 정보를 입력해주세요. 본인 확인용으로 사용됩니다."
      : "택배를 보내는 분의 정보와 픽업 주소를 입력해주세요.",
    "받는 분의 정보를 입력해주세요. 주소록에서 불러올 수도 있어요.",
    "어떤 물품을 보낼지 알려주세요. 정확한 정보일수록 안전합니다.",
    isStore
      ? "가까운 편의점을 골라주세요. 접수 후 24시간 안에 방문해 맡기시면 됩니다."
      : "택배 기사님이 방문할 날짜와 시간대를 골라주세요.",
    "예약 내용을 확인하고 결제 수단을 선택하세요.",
  ][step];
};

const FromForm = ({ data, update, method }) => (
  <>
    <div style={{
      padding: "12px 14px", background: "var(--primary-50)", borderRadius: 10,
      fontSize: 13, color: "var(--primary)", marginBottom: 20, display: "flex",
      alignItems: "center", gap: 10
    }}>
      <Icon name="sparkle" size={16}/>
      <span>
        {method === "store"
          ? "본인 확인용 정보가 자동 입력되었어요. 필요하면 수정하세요."
          : "기본 주소가 자동으로 입력되었어요. 필요하면 수정하세요."}
      </span>
    </div>
    <div className="form-grid">
      <div className="field-group">
        <label>이름</label>
        <input className="input" value={data.fromName} onChange={e => update('fromName', e.target.value)} />
      </div>
      <div className="field-group">
        <label>휴대폰</label>
        <input className="input" inputMode="numeric" maxLength={11}
          value={data.fromPhone}
          onChange={e => update('fromPhone', e.target.value.replace(/[^0-9]/g, '').slice(0, 11))}
          placeholder="01012345678"/>
        {data.fromPhone && !isValidKrMobile(data.fromPhone) && (
          <div style={{ marginTop: 4, fontSize: 12, color: "var(--danger)" }}>
            010으로 시작하는 11자리 숫자를 입력해주세요.
          </div>
        )}
      </div>
      {method !== "store" && (
        <div className="field-group full">
          <label>픽업 주소</label>
          <div style={{ display: "flex", gap: 8 }}>
            <input className="input" value={data.fromZip} readOnly
              style={{ maxWidth: 120 }} placeholder="우편번호"/>
            <button type="button" className="btn btn-outline"
              onClick={() => openPostcodePopup(({ zip, base }) => {
                update('fromZip', zip);
                update('fromAddress', base);
              })}>우편번호 찾기</button>
          </div>
          <input className="input" value={data.fromAddress} readOnly
            style={{ marginTop: 6 }} placeholder="기본 주소 (우편번호 찾기로 입력)"/>
          <input className="input" value={data.fromDetail} onChange={e => update('fromDetail', e.target.value)}
            placeholder="상세 주소 (동·호수 등)" style={{ marginTop: 6 }}/>
        </div>
      )}
    </div>
  </>
);

const ToForm = ({ data, update }) => (
  <>
    <div style={{
      display: "flex", gap: 8, marginBottom: 20, flexWrap: "wrap"
    }}>
      <button className="btn btn-outline btn-sm">
        <Icon name="user" size={14}/> 주소록에서 선택
      </button>
      <button className="btn btn-ghost btn-sm" style={{ color: "var(--muted)" }}>
        최근 받는 분: 김민지 · 이주안 · 정태우
      </button>
    </div>
    <div className="form-grid">
      <div className="field-group">
        <label>이름</label>
        <input className="input" value={data.toName} onChange={e => update('toName', e.target.value)}
          placeholder="홍길동" autoFocus/>
      </div>
      <div className="field-group">
        <label>휴대폰</label>
        <input className="input" inputMode="numeric" maxLength={11}
          value={data.toPhone}
          onChange={e => update('toPhone', e.target.value.replace(/[^0-9]/g, '').slice(0, 11))}
          placeholder="01012345678"/>
        {data.toPhone && !isValidKrMobile(data.toPhone) && (
          <div style={{ marginTop: 4, fontSize: 12, color: "var(--danger)" }}>
            010으로 시작하는 11자리 숫자를 입력해주세요.
          </div>
        )}
      </div>
      <div className="field-group full">
        <label>주소</label>
        <div style={{ display: "flex", gap: 8 }}>
          <input className="input" value={data.toZip} readOnly
            style={{ maxWidth: 120 }} placeholder="우편번호"/>
          <button type="button" className="btn btn-outline"
            onClick={() => openPostcodePopup(({ zip, base }) => {
              update('toZip', zip);
              update('toAddress', base);
            })}>우편번호 찾기</button>
        </div>
        <input className="input" value={data.toAddress} readOnly
          style={{ marginTop: 6 }} placeholder="기본 주소 (우편번호 찾기로 입력)"/>
        <input className="input" value={data.toDetail} onChange={e => update('toDetail', e.target.value)}
          placeholder="상세 주소 (동·호수 등)" style={{ marginTop: 6 }}/>
      </div>
      <div className="field-group full">
        <label>배송 메모 <span style={{ color: "var(--muted-2)", fontWeight: 400 }}>(선택)</span></label>
        <textarea
          className="textarea"
          value={data.memo}
          onChange={e => update('memo', e.target.value)}
          placeholder="문 앞에 두고 가주세요 / 경비실에 맡겨주세요"
        />
      </div>
    </div>
  </>
);

const ItemForm = ({ data, update, boxSizes }) => (
  <>
    <div className="field-group" style={{ marginBottom: 24 }}>
      <label>박스 크기</label>
      <div className="option-cards">
        {boxSizes.map(b => (
          <button key={b.id} type="button"
            className={`option-card ${data.boxSize === b.id ? "selected" : ""}`}
            onClick={() => update('boxSize', b.id)}>
            <span className="oc-title">{b.title}</span>
            <span className="oc-sub">{b.sub}</span>
            <span className="oc-price">{b.price.toLocaleString()}원</span>
          </button>
        ))}
      </div>
    </div>

    <div className="form-grid" style={{ marginBottom: 20 }}>
      <div className="field-group">
        <label>물품명</label>
        <input className="input" value={data.comodityNm}
          onChange={e => update('comodityNm', e.target.value)}
          placeholder="예: 의류, 도서, 식품"/>
      </div>
      <div className="field-group">
        <label>물품 가액</label>
        <input className="input" inputMode="numeric"
          value={data.itemPrice}
          onChange={e => update('itemPrice', e.target.value.replace(/[^0-9]/g, ''))}
          placeholder="원 (보상 한도 기준, 0원 초과)"/>
        {data.itemPrice !== "" && Number(data.itemPrice) <= 0 && (
          <div style={{ marginTop: 4, fontSize: 12, color: "var(--danger)" }}>
            1원 이상 입력해주세요.
          </div>
        )}
      </div>
    </div>
  </>
);

const ScheduleForm = ({ data, update, dates }) => (
  <>
    <div className="field-group" style={{ marginBottom: 24 }}>
      <label>픽업 날짜</label>
      <div className="dates">
        {dates.map(d => (
          <button key={d.key} type="button"
            className={`date-cell ${data.pickupDate === d.key ? "selected" : ""}`}
            onClick={() => update('pickupDate', d.key)}>
            <div className="date-day">
              {d.isToday ? "오늘" : d.isTomorrow ? "내일" : d.day}
            </div>
            <div className="date-num">{d.num}</div>
          </button>
        ))}
      </div>
    </div>

    <div className="field-group">
      <label>픽업 시간대</label>
      <div className="slot-grid" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
        {PICKUP_TIMES.map((t, i) => {
          // 오늘이 선택된 경우 09:00-11:00 슬롯 비활성 (당일 너무 이름)
          const selectedIsToday = dates.find(x => x.key === data.pickupDate)?.isToday;
          const disabled = i === 0 && selectedIsToday;
          return (
            <button key={t} type="button"
              className={`slot ${data.pickupTime === t ? "selected" : ""} ${disabled ? "disabled" : ""}`}
              onClick={() => !disabled && update('pickupTime', t)}
              disabled={disabled}>
              {t}
            </button>
          );
        })}
      </div>
      <div className="hint" style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 6 }}>
        <Icon name="clock" size={14}/>
        <span>해당 시간대 사이 30분 전 기사님이 방문 예정 SMS를 보냅니다.</span>
      </div>
    </div>
  </>
);

const ConfirmStep = ({ data, update, method, total, selectedBox }) => {
  const isStore = method === "store";
  const store = isStore ? data.store : null;
  const brand = CONV_BRANDS.find(b => b.id === data.storeBrand);
  return (
  <>
    <div className="summary" style={{ marginBottom: 20 }}>
      <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8, color: "var(--muted)" }}>예약 정보</div>

      <div style={{ display: "flex", gap: 14, padding: "12px 0", borderBottom: "1px dashed var(--line)" }}>
        <div style={{ width: 44, color: "var(--primary)" }}>
          <Icon name={isStore ? "warehouse" : "truck"} size={20}/>
        </div>
        <div style={{ flex: 1, fontSize: 14 }}>
          <div style={{ fontWeight: 600 }}>{isStore ? "편의점 택배" : "방문 택배"}</div>
          <div style={{ color: "var(--muted)", marginTop: 2 }}>
            {isStore ? "기사님 방문 없음 · 24시간 접수" : "기사님이 직접 방문해 픽업"}
          </div>
        </div>
      </div>

      <div style={{ display: "flex", gap: 14, padding: "12px 0", borderBottom: "1px dashed var(--line)" }}>
        <div style={{ width: 44, color: "var(--primary)" }}><Icon name="pin" size={20}/></div>
        <div style={{ flex: 1, fontSize: 14 }}>
          <div style={{ fontWeight: 600 }}>{data.fromName} · {data.fromPhone}</div>
          <div style={{ color: "var(--muted)", marginTop: 2 }}>
            {isStore ? "본인 확인용" : `${data.fromAddress} ${data.fromDetail}`}
          </div>
        </div>
      </div>

      <div style={{ display: "flex", gap: 14, padding: "12px 0", borderBottom: "1px dashed var(--line)" }}>
        <div style={{ width: 44, color: "var(--primary)" }}><Icon name="user" size={20}/></div>
        <div style={{ flex: 1, fontSize: 14 }}>
          <div style={{ fontWeight: 600 }}>{data.toName || "—"} {data.toPhone && `· ${data.toPhone}`}</div>
          <div style={{ color: "var(--muted)", marginTop: 2 }}>
            {data.toAddress || "받는 곳 주소"} {data.toDetail}
          </div>
        </div>
      </div>

      <div style={{ display: "flex", gap: 14, padding: "12px 0", borderBottom: "1px dashed var(--line)" }}>
        <div style={{ width: 44, color: "var(--primary)" }}><Icon name="package" size={20}/></div>
        <div style={{ flex: 1, fontSize: 14 }}>
          <div style={{ fontWeight: 600 }}>{selectedBox.title} · {data.comodityNm || "물품"}</div>
          {Number(data.itemPrice) > 0 && (
            <div style={{ color: "var(--muted)", marginTop: 2 }}>
              물품 가액 {Number(data.itemPrice).toLocaleString()}원
            </div>
          )}
        </div>
      </div>

      <div style={{ display: "flex", gap: 14, padding: "12px 0" }}>
        <div style={{ width: 44, color: "var(--primary)" }}>
          <Icon name={isStore ? "warehouse" : "calendar"} size={20}/>
        </div>
        <div style={{ flex: 1, fontSize: 14 }}>
          {isStore ? (
            <>
              <div style={{ fontWeight: 600 }}>
                {brand?.name} · {store?.name || "매장 미선택"}
              </div>
              <div style={{ color: "var(--muted)", marginTop: 2 }}>
                {store?.addr || ""} · 접수 후 24시간 내 방문
              </div>
            </>
          ) : (
            <>
              <div style={{ fontWeight: 600 }}>{data.pickupDate} · {data.pickupTime || "시간 미선택"}</div>
              <div style={{ color: "var(--muted)", marginTop: 2 }}>기사님 방문 픽업</div>
            </>
          )}
        </div>
      </div>
    </div>

    <div className="summary" style={{ marginBottom: 20 }}>
      <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 4, color: "var(--muted)" }}>결제 내역</div>
      <div className="summary-row">
        <span className="summary-label">{selectedBox.title}</span>
        <span className="summary-value">{selectedBox.price.toLocaleString()}원</span>
      </div>
      <div className="summary-row total">
        <span className="summary-label">예상 결제 금액</span>
        <span className="summary-value">{total.toLocaleString()}원</span>
      </div>
      <div style={{ fontSize: 12, color: "var(--muted)", marginTop: 6 }}>
        도서산간 추가비 등은 결제 페이지에서 최종 확정됩니다.
      </div>
    </div>

    <label className="checkbox-row" style={{
      padding: "14px 16px", background: "var(--bg-2)", borderRadius: 10
    }}>
      <input type="checkbox" checked={data.agree} onChange={e => update('agree', e.target.checked)}/>
      <span>예약 정보와 약관에 동의합니다. <span style={{ color: "var(--muted)" }}>(필수)</span></span>
    </label>
  </>
  );
};

const StoreForm = ({ data, update }) => {
  const [query, setQuery] = React.useState("");
  const [position, setPosition] = React.useState(null); // {lat, lng}
  const [geoError, setGeoError] = React.useState("");
  const [stores, setStores] = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [searchError, setSearchError] = React.useState("");

  const brand = CONV_BRANDS.find(b => b.id === data.storeBrand) || CONV_BRANDS[0];

  // 현재 위치 1회 요청
  React.useEffect(() => {
    if (!navigator.geolocation) {
      setGeoError("이 브라우저는 위치 정보를 지원하지 않습니다.");
      return;
    }
    navigator.geolocation.getCurrentPosition(
      (pos) => setPosition({ lat: pos.coords.latitude, lng: pos.coords.longitude }),
      (err) => {
        if (err.code === err.PERMISSION_DENIED) {
          setGeoError("위치 권한이 거부되어 가까운 매장 검색을 사용할 수 없어요. 매장명으로 검색해주세요.");
        } else {
          setGeoError("현재 위치를 가져올 수 없어요. 매장명으로 검색해주세요.");
        }
      },
      { timeout: 8000, maximumAge: 5 * 60 * 1000 }
    );
  }, []);

  // 브랜드/위치/검색어가 바뀌면 Kakao Places 검색
  React.useEffect(() => {
    let cancelled = false;
    setSearchError("");
    const keyword = query.trim() || brand.keyword;
    const needLocation = !query.trim();
    if (needLocation && !position) return; // 위치 도착 대기

    setLoading(true);
    (async () => {
      try {
        const kakao = await ensureKakaoMapsReady();
        const ps = new kakao.maps.services.Places();
        const options = needLocation
          ? { location: new kakao.maps.LatLng(position.lat, position.lng),
              radius: 3000, sort: kakao.maps.services.SortBy.DISTANCE, size: 15 }
          : { size: 15 };
        ps.keywordSearch(keyword, (result, status) => {
          if (cancelled) return;
          setLoading(false);
          if (status === kakao.maps.services.Status.OK) {
            setStores(result.map(r => ({
              id: r.id,
              name: r.place_name,
              addr: r.road_address_name || r.address_name || "",
              phone: r.phone || "",
              dist: r.distance ? fmtDistance(r.distance) : "",
              lat: Number(r.y), lng: Number(r.x),
            })));
          } else if (status === kakao.maps.services.Status.ZERO_RESULT) {
            setStores([]);
          } else {
            setSearchError("매장 검색 중 오류가 발생했어요. 잠시 후 다시 시도해주세요.");
          }
        }, options);
      } catch (e) {
        if (cancelled) return;
        setLoading(false);
        setSearchError("지도 서비스 로드에 실패했어요.");
      }
    })();
    return () => { cancelled = true; };
  }, [data.storeBrand, position, query]);

  return (
    <>
      <div style={{
        padding: "12px 14px", background: "var(--primary-50)", borderRadius: 10,
        fontSize: 13, color: "var(--primary)", marginBottom: 20, display: "flex",
        alignItems: "center", gap: 10
      }}>
        <Icon name="pin" size={16}/>
        <span>
          {geoError
            ? geoError
            : position
              ? "현재 위치 기준 가까운 매장 순으로 표시했어요. 접수 후 24시간 안에 방문해주세요."
              : "현재 위치를 가져오는 중이에요…"}
        </span>
      </div>

      <div className="field-group" style={{ marginBottom: 20 }}>
        <label>편의점 브랜드</label>
        <div className="store-brand-row">
          {CONV_BRANDS.map(b => (
            <button key={b.id} type="button"
              className={`store-brand ${data.storeBrand === b.id ? "selected" : ""}`}
              onClick={() => {
                update('storeBrand', b.id);
                update('store', null);
              }}>
              <div className="logo" style={{ background: b.color }}>{b.short}</div>
              <div className="name">{b.name}</div>
            </button>
          ))}
        </div>
      </div>

      <div className="field-group">
        <label>접수 매장 선택</label>
        <div className="store-search">
          <Icon name="search" size={15}/>
          <input
            type="text"
            placeholder="매장명·주소로 검색"
            value={query}
            onChange={e => setQuery(e.target.value)}
          />
        </div>
        <div className="store-list">
          {loading ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--muted)", fontSize: 13 }}>
              매장을 불러오는 중…
            </div>
          ) : searchError ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--danger)", fontSize: 13 }}>
              {searchError}
            </div>
          ) : stores.length === 0 ? (
            <div style={{ padding: 32, textAlign: "center", color: "var(--muted)", fontSize: 13 }}>
              {query
                ? `"${query}"에 해당하는 매장이 없어요.`
                : "근처에서 매장을 찾지 못했어요."}
            </div>
          ) : stores.map(s => (
            <div key={s.id}
              className={`store-item ${data.store?.id === s.id ? "selected" : ""}`}
              onClick={() => update('store', s)}>
              <div className="logo-mini" style={{ background: brand.color }}>{brand.short}</div>
              <div className="info">
                <div className="name">{s.name}</div>
                <div className="addr">{s.addr}</div>
              </div>
              <div className="meta">
                <div className="dist">{s.dist || ""}</div>
                <div className="open">{s.phone || ""}</div>
              </div>
            </div>
          ))}
        </div>
        <div className="hint" style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 6 }}>
          <Icon name="clock" size={14}/>
          <span>접수 시간: 매장 영업시간 내 · 24시간 매장은 언제든 가능</span>
        </div>
      </div>
    </>
  );
};

const DoneScreen = ({ data, method, total, navigate }) => {
  const resvId = "SLV-" + Math.floor(Math.random() * 9000000 + 1000000);
  const isStore = method === "store";
  const store = isStore ? data.store : null;
  const brand = CONV_BRANDS.find(b => b.id === data.storeBrand);
  return (
    <div className="reserve-shell" style={{ gridTemplateColumns: "1fr", maxWidth: 620 }}>
      <section className="reserve-main">
        <div className="done-card">
          <div className="done-icon">
            <Icon name="check-bold" size={36}/>
          </div>
          <h3>예약이 완료되었어요</h3>
          <p>
            {isStore
              ? `${brand?.name}에 24시간 내 방문해 택배를 맡겨주세요.`
              : "예약 정보를 카카오톡으로 보내드렸어요."}
          </p>

          <div className="done-receipt">
            <div className="done-receipt-row id">
              <span className="lbl">예약 번호</span>
              <span className="val">{resvId}</span>
            </div>
            <div className="done-receipt-row">
              <span className="lbl">{isStore ? "접수처" : "픽업"}</span>
              <span className="val">
                {isStore
                  ? `${store?.name || "매장"}`
                  : `${data.pickupDate} · ${data.pickupTime}`}
              </span>
            </div>
            <div className="done-receipt-row">
              <span className="lbl">받는 분</span>
              <span className="val">{data.toName || "—"}</span>
            </div>
            <div className="done-receipt-row">
              <span className="lbl">결제</span>
              <span className="val">{total.toLocaleString()}원 · {{
                naver: "네이버페이", kakao: "카카오페이", card: "카드"
              }[data.payment]}</span>
            </div>
          </div>

          <div style={{ display: "flex", gap: 10, justifyContent: "center" }}>
            <button className="btn btn-outline" onClick={() => navigate('my')}>
              내 예약 보기
            </button>
            <button className="btn btn-primary" onClick={() => navigate('my')}>
              홈으로 <Icon name="arrow-right" size={14}/>
            </button>
          </div>
        </div>
      </section>
    </div>
  );
};

window.Reservation = Reservation;
window.ReserveMethodPicker = ReserveMethodPicker;
