/* ============================================================================
 * 서명전에 — TDS Toast 컴포넌트
 *  · 화면 상/하단에 잠깐 떠올라 작업 완료·이벤트를 알리는 피드백 메시지.
 *    오버레이로 본문을 가리지 않으며 자동으로 사라지고, 사용자가 손가락으로
 *    스와이프(상단=위로 / 하단=아래로)해 즉시 닫을 수도 있어요.
 *
 *  · 4 컴포넌트:
 *      Toast         — 본체 (Portal 마운트, controlled open + onClose)
 *      Toast.Icon    — leftAddon 용 아이콘 (name + frameShape)
 *      Toast.Lottie  — leftAddon 용 Lottie (src + frameShape, lottie-web 없으면 SVG 폴백)
 *      Toast.Button  — 하단 토스트 우측 액션 버튼 (children + onClick)
 *
 *  · 라이프사이클 (controlled):
 *      open=false → mount 안 함.
 *      open false→true: 마운트 + RAF 두 번 후 visible 클래스 부여 → slide+fade in.
 *      visible=true 동안 duration 타이머 시작 (button 있으면 기본 5000, 아니면 3000).
 *        호버/터치/포커스 동안 일시정지(보고 있는 동안 안 사라지게).
 *      타이머 만료 OR 드래그 닫힘 OR 외부에서 open=false → onClose() 호출.
 *      open=false 감지 → visible 해제 → 280ms 후 unmount + onExited().
 *
 *  · 시각:
 *      grey-900 배경 / 흰 텍스트 / 14px 라운드 / shadow-elevation-medium /
 *      가로 가운데 정렬, max-width: calc(100vw - 32px), min-width: 0.
 *      좌측 leftAddon (24×24 기본) + 본문 + 우측 button(있으면) 한 행 정렬, gap 12px.
 *      position=top: top env(safe-area-inset-top) + 16px, slide -8px.
 *      position=bottom: bottom env(safe-area-inset-bottom) + 16px, slide +8px.
 *      higherThanCTA + bottom: bottom 값에 88px 추가 (FixedBottomCTA 영역 회피).
 *
 *  · 드래그-닫기:
 *      pointerdown → setPointerCapture → pointermove 로 dy 추적.
 *      top: dy < -40px 면 닫힘 / bottom: dy > 40px 면 닫힘 (반대 방향은 50% 감쇠).
 *      pointerup 시 임계 미만이면 위치 복원 (transform reset).
 *
 *  · 접근성:
 *      role="status" + aria-live (기본 polite, 중요한 알림은 assertive).
 *      텍스트가 곧 스크린 리더 발화 — 별도 라벨 불필요.
 *      Toast.Icon / Toast.Lottie 의 시각 요소는 aria-hidden=true.
 *      Toast.Button 은 안에 있는 button 그대로 — 키보드 접근 가능.
 *
 *  · 모션:
 *      slide+fade 280ms cubic-bezier(0.22, 1, 0.36, 1).
 *      prefers-reduced-motion 시 transform 없이 fade 만 120ms.
 * ========================================================================== */

const { useState: __toUseState, useEffect: __toUseEffect, useRef: __toUseRef } = React;

const __TOAST_DEFAULT_DURATION = 3000;
const __TOAST_DURATION_WITH_BUTTON = 5000;
const __TOAST_EXIT_MS = 280;
const __TOAST_DRAG_THRESHOLD = 40;

/* ============================================================================
 * Toast (root)
 * ========================================================================== */

function Toast({
  open,
  position,
  text,
  leftAddon,
  button,
  duration,
  onClose,
  onExited,
  higherThanCTA = false,
  "aria-live": ariaLive = "polite",
  portalContainer,
  className = "",
  style,
  id,
  ...rest
}) {
  // ----- props 검증 -----
  if (position !== "top" && position !== "bottom") {
    if (window.console && console.warn) {
      console.warn(
        `[Toast] position 은 필수 — "top" | "bottom". 받은 값: ${position}. "bottom" 폴백.`
      );
    }
    position = "bottom";
  }
  if (typeof text !== "string" || text.length === 0) {
    if (window.console && console.warn) {
      console.warn("[Toast] text 는 필수 (비어 있지 않은 string).");
    }
  }
  if (button && position === "top") {
    if (window.console && console.warn) {
      console.warn("[Toast] button 은 position='bottom' 에서만 사용 가능. 무시함.");
    }
    button = null;
  }
  if (higherThanCTA && position === "top") {
    if (window.console && console.warn) {
      console.warn("[Toast] higherThanCTA 는 position='bottom' 에서만 의미 있음. 무시함.");
    }
    higherThanCTA = false;
  }

  // ----- state -----
  const [mounted, setMounted] = __toUseState(!!open);
  const [visible, setVisible] = __toUseState(false);
  const [dragDy, setDragDy] = __toUseState(0);
  const [paused, setPaused] = __toUseState(false);

  const elRef = __toUseRef(null);
  const timerRef = __toUseRef(null);
  const exitTimerRef = __toUseRef(null);
  const rafRef = __toUseRef(null);
  const pointerStateRef = __toUseRef(null); // {pointerId, startY, dy}

  // 효과적 duration — button 있으면 5초, 아니면 3초. 사용자가 명시하면 그 값.
  const effectiveDuration =
    typeof duration === "number" && isFinite(duration) && duration > 0
      ? duration
      : button
        ? __TOAST_DURATION_WITH_BUTTON
        : __TOAST_DEFAULT_DURATION;

  // ----- 마운트/언마운트 + visible 토글 -----
  __toUseEffect(function () {
    if (open) {
      // 1) 마운트 (mounted=false → true)
      setMounted(true);
      // 2) 다음 프레임에 visible=true 로 transition 시작
      //    RAF 두 번 — 첫 RAF 는 마운트 후 paint, 두 번째 RAF 는 transition 시작점.
      const id1 = requestAnimationFrame(function () {
        rafRef.current = requestAnimationFrame(function () {
          setVisible(true);
        });
      });
      return function () {
        cancelAnimationFrame(id1);
        if (rafRef.current) cancelAnimationFrame(rafRef.current);
      };
    } else {
      // open=false → visible 해제 → exit 타이머 → 언마운트
      setVisible(false);
    }
  }, [open]);

  // ----- 자동 닫힘 타이머 -----
  __toUseEffect(function () {
    if (!visible || !open) return;
    if (paused) return;
    timerRef.current = setTimeout(function () {
      if (typeof onClose === "function") onClose();
    }, effectiveDuration);
    return function () {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
    };
  }, [visible, open, paused, effectiveDuration, onClose]);

  // ----- exit 후 unmount + onExited -----
  __toUseEffect(function () {
    if (mounted && !open && !visible) {
      exitTimerRef.current = setTimeout(function () {
        setMounted(false);
        setDragDy(0);
        if (typeof onExited === "function") onExited();
      }, __TOAST_EXIT_MS);
      return function () {
        if (exitTimerRef.current) {
          clearTimeout(exitTimerRef.current);
          exitTimerRef.current = null;
        }
      };
    }
  }, [mounted, open, visible, onExited]);

  // ----- 드래그-닫기 -----
  const handlePointerDown = function (e) {
    if (!visible) return;
    // 좌클릭 / 터치 / 펜만
    if (e.button !== undefined && e.button !== 0) return;
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) { /* ignore */ }
    pointerStateRef.current = {
      pointerId: e.pointerId,
      startY: e.clientY,
      dy: 0,
    };
    setPaused(true);
  };
  const handlePointerMove = function (e) {
    const s = pointerStateRef.current;
    if (!s || s.pointerId !== e.pointerId) return;
    let dy = e.clientY - s.startY;
    // 닫는 방향이 아닌 쪽은 50% 감쇠 (저항감)
    if (position === "top" && dy > 0) dy = dy * 0.5;
    if (position === "bottom" && dy < 0) dy = dy * 0.5;
    s.dy = dy;
    setDragDy(dy);
  };
  const handlePointerEnd = function (e) {
    const s = pointerStateRef.current;
    if (!s || s.pointerId !== e.pointerId) return;
    const finalDy = s.dy;
    pointerStateRef.current = null;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) { /* ignore */ }
    setDragDy(0);
    setPaused(false);

    const closingMet =
      (position === "top" && finalDy < -__TOAST_DRAG_THRESHOLD) ||
      (position === "bottom" && finalDy > __TOAST_DRAG_THRESHOLD);
    if (closingMet) {
      if (typeof onClose === "function") onClose();
    }
  };

  // ----- pause on hover/focus -----
  const handlePauseEnter = function () { setPaused(true); };
  const handlePauseLeave = function () { setPaused(false); };

  if (!mounted) return null;

  // ----- 클래스 -----
  const cls = [
    "tds-toast",
    `tds-toast--${position}`,
    visible ? "is-visible" : "is-leaving",
    higherThanCTA ? "tds-toast--higher-cta" : "",
    button ? "tds-toast--has-button" : "",
    leftAddon ? "tds-toast--has-left" : "",
    paused ? "is-paused" : "",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  // ----- inline style — 드래그 transform 주입 -----
  // 드래그 중에는 base translate 위에 dy 만 더해진 transform 으로 즉시 추적
  // (CSS visible/leaving 클래스의 transform 보다 우선)
  const dragStyle = dragDy !== 0
    ? {
        transform: `translate3d(-50%, ${dragDy}px, 0)`,
        transition: "none",
      }
    : undefined;

  const finalStyle = { ...(style || {}), ...(dragStyle || {}) };

  // ----- portal 결정 -----
  const portalRoot =
    portalContainer ||
    (typeof document !== "undefined" ? document.body : null);
  if (!portalRoot) return null;

  return ReactDOM.createPortal(
    <div
      id={id}
      ref={elRef}
      className={cls}
      style={finalStyle}
      role="status"
      aria-live={ariaLive}
      aria-atomic="true"
      onPointerDown={handlePointerDown}
      onPointerMove={handlePointerMove}
      onPointerUp={handlePointerEnd}
      onPointerCancel={handlePointerEnd}
      onMouseEnter={handlePauseEnter}
      onMouseLeave={handlePauseLeave}
      onFocus={handlePauseEnter}
      onBlur={handlePauseLeave}
      {...rest}
    >
      {leftAddon ? (
        <div className="tds-toast__left" aria-hidden="true">{leftAddon}</div>
      ) : null}
      <div className="tds-toast__text">{text}</div>
      {button ? (
        <div className="tds-toast__button">{button}</div>
      ) : null}
    </div>,
    portalRoot
  );
}

/* ============================================================================
 * Toast.Icon
 *  · name 으로 빌트인 SVG 패턴 (success/warning/error/info) 매핑.
 *  · IOSIcon 이름이 매칭되면 IOSIcon 으로 위임.
 *  · 외부 URL(.svg/.png) 이면 <img> 로 표시.
 *  · frameShape={width, height, radius} 로 사이즈/모서리 제어 (기본 24×24).
 * ========================================================================== */

const __TOAST_BUILTIN_ICONS = {
  // 성공 — 초록 동그라미 + 흰 체크
  "icn-success-color": { color: "#1ec55a", glyph: "check" },
  // 경고 — 노랑 삼각/동그라미 + 흰 !
  "icn-warning-color": { color: "#ffb020", glyph: "exclaim" },
  // 에러 — 빨강 동그라미 + 흰 X
  "icn-error-color": { color: "#f04452", glyph: "x" },
  // 정보 — 블루 동그라미 + 흰 i
  "icn-info-color":   { color: "#3182f6", glyph: "info" },
};

function ToastIcon({
  name,
  icon,
  frameShape,
  className = "",
  style,
  ...rest
}) {
  const finalName = name || icon;
  if (!finalName) {
    if (window.console && console.warn) {
      console.warn("[Toast.Icon] name 은 필수예요.");
    }
    return null;
  }
  const fs = frameShape || {};
  const w = typeof fs.width === "number" ? `${fs.width}px` : (fs.width || "24px");
  const h = typeof fs.height === "number" ? `${fs.height}px` : (fs.height || "24px");
  const r = fs.radius != null
    ? (typeof fs.radius === "number" ? `${fs.radius}px` : fs.radius)
    : undefined;

  const wrapStyle = {
    width: w,
    height: h,
    borderRadius: r,
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    flex: "0 0 auto",
    ...(style || {}),
  };

  // 빌트인 색 아이콘
  const builtin = __TOAST_BUILTIN_ICONS[finalName];
  if (builtin) {
    return (
      <span
        className={["tds-toast-icon", "tds-toast-icon--builtin", className].filter(Boolean).join(" ")}
        style={wrapStyle}
        aria-hidden="true"
        {...rest}
      >
        <svg width="100%" height="100%" viewBox="0 0 24 24" fill="none" focusable="false">
          <circle cx="12" cy="12" r="11" fill={builtin.color} />
          {builtin.glyph === "check" && (
            <polyline points="6.5 12.5 10.5 16.5 17.5 8.5"
              stroke="#ffffff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" fill="none" />
          )}
          {builtin.glyph === "exclaim" && (
            <g>
              <rect x="11" y="6.5" width="2" height="8" rx="1" fill="#ffffff" />
              <circle cx="12" cy="17" r="1.4" fill="#ffffff" />
            </g>
          )}
          {builtin.glyph === "x" && (
            <g stroke="#ffffff" strokeWidth="2.4" strokeLinecap="round">
              <line x1="8" y1="8" x2="16" y2="16" />
              <line x1="16" y1="8" x2="8" y2="16" />
            </g>
          )}
          {builtin.glyph === "info" && (
            <g fill="#ffffff">
              <circle cx="12" cy="7.5" r="1.4" />
              <rect x="11" y="10.5" width="2" height="8" rx="1" />
            </g>
          )}
        </svg>
      </span>
    );
  }

  // URL (http/https/data 또는 확장자) → <img>
  const isUrl = /^(https?:|data:|\/)/.test(finalName) || /\.(svg|png|jpg|jpeg|webp|gif)$/i.test(finalName);
  if (isUrl) {
    return (
      <span
        className={["tds-toast-icon", "tds-toast-icon--img", className].filter(Boolean).join(" ")}
        style={wrapStyle}
        aria-hidden="true"
        {...rest}
      >
        <img src={finalName} alt="" style={{ width: "100%", height: "100%", display: "block" }} />
      </span>
    );
  }

  // IOSIcon 위임 (전역 IOSIcon 이 있을 때)
  if (typeof window !== "undefined" && typeof window.IOSIcon === "function") {
    const sizePx = typeof fs.width === "number" ? fs.width : 24;
    return (
      <span
        className={["tds-toast-icon", "tds-toast-icon--ios", className].filter(Boolean).join(" ")}
        style={wrapStyle}
        aria-hidden="true"
        {...rest}
      >
        {React.createElement(window.IOSIcon, { name: finalName, size: sizePx })}
      </span>
    );
  }

  // 마지막 폴백 — 회색 동그라미
  return (
    <span
      className={["tds-toast-icon", "tds-toast-icon--fallback", className].filter(Boolean).join(" ")}
      style={{ ...wrapStyle, background: "var(--tds-grey-300, #d1d6db)" }}
      aria-hidden="true"
      {...rest}
    />
  );
}

Toast.Icon = ToastIcon;

/* ============================================================================
 * Toast.Lottie
 *  · src 로 Lottie JSON URL.
 *  · window.lottie (lottie-web) 가 로드돼 있으면 진짜 Lottie 재생.
 *  · 없으면 src 파일명 힌트(check/success/warning/error)로 SVG 폴백.
 * ========================================================================== */

function ToastLottie({
  src,
  frameShape,
  className = "",
  style,
  ...rest
}) {
  if (!src) {
    if (window.console && console.warn) {
      console.warn("[Toast.Lottie] src 는 필수예요.");
    }
    return null;
  }

  const fs = frameShape || {};
  const w = typeof fs.width === "number" ? `${fs.width}px` : (fs.width || "24px");
  const h = typeof fs.height === "number" ? `${fs.height}px` : (fs.height || "24px");
  const r = fs.radius != null
    ? (typeof fs.radius === "number" ? `${fs.radius}px` : fs.radius)
    : undefined;

  const wrapStyle = {
    width: w,
    height: h,
    borderRadius: r,
    display: "inline-flex",
    alignItems: "center",
    justifyContent: "center",
    flex: "0 0 auto",
    ...(style || {}),
  };

  const containerRef = __toUseRef(null);

  // lottie-web 시도
  __toUseEffect(function () {
    let anim = null;
    let cancelled = false;
    if (
      typeof window !== "undefined" &&
      window.lottie &&
      typeof window.lottie.loadAnimation === "function" &&
      containerRef.current
    ) {
      try {
        anim = window.lottie.loadAnimation({
          container: containerRef.current,
          renderer: "svg",
          loop: true,
          autoplay: true,
          path: src,
        });
      } catch (e) {
        anim = null;
        if (window.console && console.warn) console.warn("[Toast.Lottie] 재생 실패:", e);
      }
    }
    return function () {
      cancelled = true;
      if (anim && typeof anim.destroy === "function") {
        try { anim.destroy(); } catch (_) { /* ignore */ }
      }
    };
  }, [src]);

  // 폴백 SVG — src 힌트로 색/모양 결정
  const lower = (src || "").toLowerCase();
  let fallbackKey = null;
  if (/check|success/.test(lower)) fallbackKey = "icn-success-color";
  else if (/warn/.test(lower))     fallbackKey = "icn-warning-color";
  else if (/error|fail|danger/.test(lower)) fallbackKey = "icn-error-color";
  else if (/info/.test(lower))     fallbackKey = "icn-info-color";

  const hasLottie = typeof window !== "undefined" && !!window.lottie;

  return (
    <span
      className={["tds-toast-lottie", className].filter(Boolean).join(" ")}
      style={wrapStyle}
      aria-hidden="true"
      {...rest}
    >
      {/* lottie-web 가 있으면 이 div 안에 SVG 가 채워짐 */}
      <span
        ref={containerRef}
        style={{
          width: "100%",
          height: "100%",
          display: hasLottie ? "block" : "none",
        }}
      />
      {/* 폴백 — 색 SVG */}
      {!hasLottie && fallbackKey ? (
        <ToastIcon name={fallbackKey} frameShape={{ width: parseFloat(w) || 24, height: parseFloat(h) || 24 }} />
      ) : !hasLottie ? (
        <span
          style={{
            width: "100%",
            height: "100%",
            borderRadius: "50%",
            background: "var(--tds-grey-300, #d1d6db)",
          }}
        />
      ) : null}
    </span>
  );
}

Toast.Lottie = ToastLottie;

/* ============================================================================
 * Toast.Button
 *  · 하단 토스트 우측 액션 버튼. 어두운 토스트 위 흰 텍스트, weight bold.
 *  · type="button" 으로 submit 방지. 키보드 접근 가능 (그냥 button).
 * ========================================================================== */

function ToastButton({ children, onClick, className = "", style, ...rest }) {
  const handleClick = function (e) {
    if (typeof onClick === "function") onClick(e);
  };
  return (
    <button
      type="button"
      className={["tds-toast-button", className].filter(Boolean).join(" ")}
      style={style}
      onClick={handleClick}
      {...rest}
    >
      {children}
    </button>
  );
}

Toast.Button = ToastButton;

window.Toast = Toast;
window.ToastIcon = ToastIcon;
window.ToastLottie = ToastLottie;
window.ToastButton = ToastButton;
Object.assign(window, { Toast, ToastIcon, ToastLottie, ToastButton });
