/* ============================================================================
 * 서명전에 — TDS Overlay Extension (Task #170 #171 #172)
 *
 *  · TDS Mobile 2026-03 Overlay Extension 스펙 — useDialog / useToast / useBottomSheet
 *    세 개의 훅(hook)을 제공해, 기존 컴포넌트를 명령형으로 띄울 수 있게 해요.
 *
 *    useDialog       → openAlert / openConfirm / openAsyncConfirm
 *                      (AlertDialog #161 · ConfirmDialog #162 래핑)
 *    useToast        → openToast / closeToast
 *                      (Toast #154 래핑 — 웹 3000ms · 앱 5000ms 기본)
 *    useBottomSheet  → open / close
 *                      openOneButtonSheet / openTwoButtonSheet / openAsyncTwoButtonSheet
 *                      (BottomSheet #125 래핑)
 *
 *  · 구조 — 모듈 스코프 싱글턴 `__OverlayManager` 가 Map<id, descriptor> 를 들고
 *    `<OverlayRoot>` 컴포넌트가 그 상태를 구독해 document.body 위에 모든 오버레이를
 *    렌더링. 훅은 manager 의 push/pop 메서드만 노출하는 thin wrapper.
 *    → 같은 페이지에서 여러 컴포넌트가 동시에 훅을 호출해도 충돌 없이 쌓여요.
 *
 *  · Async 메서드:
 *      openAsyncConfirm             → Promise<boolean>  (confirm=true / cancel=false)
 *      openAsyncTwoButtonSheet      → Promise<boolean>  (right=true / left=false)
 *    Promise resolve 직후 오버레이는 자동으로 닫혀요. reject 는 없어요 — 사용자
 *    취소(뒤로가기 · 딤 · ESC) 도 false 로 resolve.
 *
 *  · ID 할당 — `++__overlayIdSeq` 단순 증가. 동일 훅 호출에서 open 이 여러 번
 *    호출돼도 기존을 close 하지 않고 stack — 원하는 동작이 아닐 경우 호출자가
 *    close 후 다시 open.
 *
 *  · 환경 감지 (Toast duration) — window.TossAdapter.isInToss() 가 true 면 5000ms,
 *    아니면 3000ms. button 이 있으면 duration 관계없이 기본 5000ms (Toast 컴포넌트
 *    내부 로직 그대로 유지 — 오버라이드하지 않음).
 *
 *  · UNSAFE_ 플래그 — BottomSheet 옵션은 그대로 패스스루. 접근성 우려가 있어
 *    이름에 UNSAFE_ 접두사. 사용 전 접근성 팀 리뷰 필요.
 *
 *  · 접근성:
 *      - 오버레이 오픈 시 트리거 요소(activeElement) 를 기억, 닫힐 때 포커스 복원.
 *      - 여러 오버레이가 쌓이면 가장 최근 것만 포커스 트랩(기본) — bottomsheet 의
 *        UNSAFE_disableFocusLock 으로 오버라이드 가능.
 *      - reduce-motion 은 개별 컴포넌트 CSS 에서 이미 처리.
 *
 *  · Babel Standalone 환경 호환: __ox 접두사 alias.
 * ========================================================================== */

const __oxUseState     = (typeof React !== "undefined" && React.useState) || function (v) { return [v, function () {}]; };
const __oxUseEffect    = (typeof React !== "undefined" && React.useEffect) || function () {};
const __oxUseCallback  = (typeof React !== "undefined" && React.useCallback) || function (fn) { return fn; };
const __oxUseMemo      = (typeof React !== "undefined" && React.useMemo) || function (fn) { return fn(); };
const __oxUseRef       = (typeof React !== "undefined" && React.useRef) || function (v) { return { current: v }; };

/* ----------------------------------------------------------------------------
 * 싱글턴 manager
 * -------------------------------------------------------------------------- */
let __overlayIdSeq = 0;
const __overlayListeners = new Set();
/**
 * overlays: Array<{
 *   id: number,
 *   kind: "alert" | "confirm" | "toast" | "bottomsheet",
 *   props: object,
 *   // 내부 상태 — closing 이면 open=false 로 바뀌어 퇴장 애니메이션 중
 *   closing: boolean,
 *   // activeElement 복원용
 *   trigger: HTMLElement | null,
 *   // async 해제 용 (confirm / two-button-sheet 에서만 사용)
 *   resolve: ((v: boolean) => void) | null,
 * }>
 */
const __overlayState = { overlays: [] };

function __oxEmit() {
  const listeners = Array.from(__overlayListeners);
  for (let i = 0; i < listeners.length; i += 1) {
    try { listeners[i](); } catch (e) {
      if (window.console && console.warn) console.warn("[Overlay] listener 실패", e);
    }
  }
}

function __oxPush(kind, props, resolve) {
  const id = ++__overlayIdSeq;
  const trigger = (typeof document !== "undefined" && document.activeElement instanceof HTMLElement)
    ? document.activeElement
    : null;
  __overlayState.overlays = __overlayState.overlays.concat([{
    id: id,
    kind: kind,
    props: props || {},
    closing: false,
    trigger: trigger,
    resolve: resolve || null,
  }]);
  __oxEmit();
  return id;
}

function __oxUpdate(id, patch) {
  let changed = false;
  __overlayState.overlays = __overlayState.overlays.map(function (o) {
    if (o.id !== id) return o;
    changed = true;
    return Object.assign({}, o, patch);
  });
  if (changed) __oxEmit();
}

function __oxClose(id, asyncValue) {
  let target = null;
  __overlayState.overlays = __overlayState.overlays.map(function (o) {
    if (o.id !== id) return o;
    target = o;
    return Object.assign({}, o, { closing: true });
  });
  if (target) {
    if (typeof target.resolve === "function") {
      try { target.resolve(!!asyncValue); } catch (_) {}
    }
    __oxEmit();
  }
}

function __oxRemove(id) {
  const before = __overlayState.overlays.length;
  let target = null;
  __overlayState.overlays = __overlayState.overlays.filter(function (o) {
    if (o.id === id) { target = o; return false; }
    return true;
  });
  if (before !== __overlayState.overlays.length) {
    // 포커스 복원
    if (target && target.trigger && typeof target.trigger.focus === "function" && document.contains(target.trigger)) {
      try { target.trigger.focus(); } catch (_) {}
    }
    __oxEmit();
  }
}

function __oxSubscribe(listener) {
  __overlayListeners.add(listener);
  return function () { __overlayListeners.delete(listener); };
}

/* ----------------------------------------------------------------------------
 * OverlayRoot — document.body 에 싱글 마운트되어 모든 오버레이 렌더
 * -------------------------------------------------------------------------- */
function OverlayRoot() {
  const [tick, setTick] = __oxUseState(0);
  __oxUseEffect(function () {
    const unsubscribe = __oxSubscribe(function () {
      setTick(function (n) { return n + 1; });
    });
    return unsubscribe;
  }, []);

  // tick 은 재렌더 트리거용 — 실제 값은 __overlayState 에서 읽음
  void tick;

  const nodes = [];
  const overlays = __overlayState.overlays;
  for (let i = 0; i < overlays.length; i += 1) {
    const o = overlays[i];
    const node = __oxRender(o);
    if (node) nodes.push(node);
  }

  return React.createElement(React.Fragment, null, nodes);
}

/* ----------------------------------------------------------------------------
 * __oxRender — 종류별 JSX
 * -------------------------------------------------------------------------- */
function __oxRender(o) {
  if (o.kind === "alert")       return __oxRenderAlert(o);
  if (o.kind === "confirm")     return __oxRenderConfirm(o);
  if (o.kind === "toast")       return __oxRenderToast(o);
  if (o.kind === "bottomsheet") return __oxRenderBottomSheet(o);
  return null;
}

// ── Alert ──────────────────────────────────────────────────────────────
function __oxRenderAlert(o) {
  const p = o.props || {};
  if (typeof window.AlertDialog !== "function") {
    if (window.console && console.warn) console.warn("[Overlay] AlertDialog 미로드 — openAlert 무시");
    return null;
  }
  const AD = window.AlertDialog;
  const open = !o.closing;

  const titleNode = (p.title != null && typeof p.title === "string")
    ? React.createElement(AD.Title, null, p.title)
    : p.title;
  const descNode = (p.description != null && typeof p.description === "string")
    ? React.createElement(AD.Description, null, p.description)
    : p.description;
  const buttonLabel = p.buttonText || p.confirmLabel || "확인";
  const buttonNode = React.createElement(
    AD.AlertButton,
    {
      onClick: function () {
        try { if (typeof p.onConfirm === "function") p.onConfirm(); } catch (e) {
          if (window.console && console.warn) console.warn("[Overlay] onConfirm 예외", e);
        }
        __oxClose(o.id, true);
      },
    },
    buttonLabel
  );

  return React.createElement(AD, {
    key: "ox-alert-" + o.id,
    open: open,
    title: titleNode,
    description: descNode,
    alertButton: buttonNode,
    closeOnDimmerClick: p.closeOnDimmerClick !== false,
    closeOnBackEvent: p.closeOnBackEvent !== false,
    onClose: function () { __oxClose(o.id, false); },
    onExited: function () { __oxRemove(o.id); },
  });
}

// ── Confirm ────────────────────────────────────────────────────────────
function __oxRenderConfirm(o) {
  const p = o.props || {};
  if (typeof window.ConfirmDialog !== "function") {
    if (window.console && console.warn) console.warn("[Overlay] ConfirmDialog 미로드 — openConfirm 무시");
    return null;
  }
  const CD = window.ConfirmDialog;
  const open = !o.closing;

  const titleNode = (p.title != null && typeof p.title === "string")
    ? React.createElement(CD.Title, null, p.title)
    : p.title;
  const descNode = (p.description != null && typeof p.description === "string")
    ? React.createElement(CD.Description, null, p.description)
    : p.description;

  const cancelLabel  = p.cancelLabel  || p.cancelText  || "취소";
  const confirmLabel = p.confirmLabel || p.confirmText || "확인";

  const cancelNode = React.createElement(
    CD.CancelButton,
    {
      onClick: function () {
        try { if (typeof p.onCancel === "function") p.onCancel(); } catch (e) {
          if (window.console && console.warn) console.warn("[Overlay] onCancel 예외", e);
        }
        __oxClose(o.id, false);
      },
    },
    cancelLabel
  );

  const confirmNode = React.createElement(
    CD.ConfirmButton,
    {
      onClick: function () {
        try { if (typeof p.onConfirm === "function") p.onConfirm(); } catch (e) {
          if (window.console && console.warn) console.warn("[Overlay] onConfirm 예외", e);
        }
        __oxClose(o.id, true);
      },
    },
    confirmLabel
  );

  return React.createElement(CD, {
    key: "ox-confirm-" + o.id,
    open: open,
    title: titleNode,
    description: descNode,
    cancelButton: cancelNode,
    confirmButton: confirmNode,
    buttonOrder: p.buttonOrder || "cancel-first",
    forceVerticalButtons: !!p.forceVerticalButtons,
    closeOnDimmerClick: p.closeOnDimmerClick !== false,
    closeOnBackEvent: p.closeOnBackEvent !== false,
    onClose: function () { __oxClose(o.id, false); },
    onExited: function () { __oxRemove(o.id); },
  });
}

// ── Toast ──────────────────────────────────────────────────────────────
function __oxRenderToast(o) {
  const p = o.props || {};
  if (typeof window.Toast !== "function") {
    if (window.console && console.warn) console.warn("[Overlay] Toast 미로드 — openToast 무시");
    return null;
  }
  const T = window.Toast;
  const open = !o.closing;

  // icon / lottie 슬롯 구성
  let leftAddon = null;
  if (p.icon && typeof T.Icon === "function") {
    if (typeof p.icon === "string") {
      leftAddon = React.createElement(T.Icon, { name: p.icon });
    } else if (typeof p.icon === "object" && p.icon.name) {
      leftAddon = React.createElement(T.Icon, p.icon);
    } else {
      leftAddon = p.icon; // ReactNode 그대로
    }
  } else if (p.lottie && typeof T.Lottie === "function") {
    if (typeof p.lottie === "string") {
      leftAddon = React.createElement(T.Lottie, { src: p.lottie });
    } else if (typeof p.lottie === "object" && p.lottie.src) {
      leftAddon = React.createElement(T.Lottie, p.lottie);
    } else {
      leftAddon = p.lottie;
    }
  } else if (p.leftAddon) {
    leftAddon = p.leftAddon;
  }

  // button 슬롯
  let buttonNode = null;
  if (p.button && typeof T.Button === "function") {
    if (typeof p.button === "object" && (p.button.label != null || p.button.children != null)) {
      const label = p.button.label != null ? p.button.label : p.button.children;
      const onBtnClick = p.button.onClick;
      buttonNode = React.createElement(
        T.Button,
        {
          onClick: function () {
            try { if (typeof onBtnClick === "function") onBtnClick(); } catch (e) {
              if (window.console && console.warn) console.warn("[Overlay] toast button onClick 예외", e);
            }
            __oxClose(o.id, true);
          },
        },
        label
      );
    } else {
      buttonNode = p.button;
    }
  }

  return React.createElement(T, {
    key: "ox-toast-" + o.id,
    open: open,
    position: p.position || "bottom",
    text: p.text || "",
    leftAddon: leftAddon,
    button: buttonNode,
    duration: p.duration,
    higherThanCTA: !!p.higherThanCTA,
    "aria-live": p["aria-live"] || "polite",
    onClose: function () { __oxClose(o.id, false); },
    onExited: function () { __oxRemove(o.id); },
  });
}

// ── BottomSheet ────────────────────────────────────────────────────────
function __oxRenderBottomSheet(o) {
  const p = o.props || {};
  if (typeof window.BottomSheet !== "function") {
    if (window.console && console.warn) console.warn("[Overlay] BottomSheet 미로드 — open 무시");
    return null;
  }
  const BS = window.BottomSheet;
  const open = !o.closing;

  // header / description 슬롯
  let headerNode = p.header || null;
  if (headerNode == null && p.title != null) {
    headerNode = typeof BS.Header === "function"
      ? React.createElement(BS.Header, null, p.title)
      : p.title;
  }

  let descNode = p.headerDescription || null;
  if (descNode == null && p.description != null) {
    descNode = typeof BS.HeaderDescription === "function"
      ? React.createElement(BS.HeaderDescription, null, p.description)
      : p.description;
  }

  // cta 슬롯 — variant 에 따라 구성
  let ctaNode = p.cta || null;
  const variant = p.__variant; // "one-button" | "two-button" | 없으면 children 만
  if (!ctaNode && variant === "one-button" && typeof BS.CTA === "function") {
    ctaNode = React.createElement(
      BS.CTA,
      {
        onClick: function () {
          try { if (typeof p.onConfirm === "function") p.onConfirm(); } catch (e) {
            if (window.console && console.warn) console.warn("[Overlay] bottomsheet onConfirm 예외", e);
          }
          __oxClose(o.id, true);
        },
      },
      p.confirmLabel || p.buttonText || "확인"
    );
  } else if (!ctaNode && variant === "two-button" && typeof BS.DoubleCTA === "function") {
    // leftButton / rightButton 은 TDS Button 을 기대 — window.Button 있으면 사용, 없으면 native
    const BtnFallback = typeof window.Button === "function" ? window.Button : null;
    const leftLabel  = p.cancelLabel  || p.cancelText  || "취소";
    const rightLabel = p.confirmLabel || p.confirmText || "확인";
    const leftBtn = BtnFallback
      ? React.createElement(BtnFallback, {
          color: "secondary",
          variant: "fill",
          size: "xlarge",
          display: "block",
          onClick: function () {
            try { if (typeof p.onCancel === "function") p.onCancel(); } catch (e) {
              if (window.console && console.warn) console.warn("[Overlay] bottomsheet onCancel 예외", e);
            }
            __oxClose(o.id, false);
          },
        }, leftLabel)
      : React.createElement("button", {
          type: "button",
          onClick: function () {
            try { if (typeof p.onCancel === "function") p.onCancel(); } catch (_) {}
            __oxClose(o.id, false);
          },
        }, leftLabel);

    const rightBtn = BtnFallback
      ? React.createElement(BtnFallback, {
          color: "primary",
          variant: "fill",
          size: "xlarge",
          display: "block",
          onClick: function () {
            try { if (typeof p.onConfirm === "function") p.onConfirm(); } catch (e) {
              if (window.console && console.warn) console.warn("[Overlay] bottomsheet onConfirm 예외", e);
            }
            __oxClose(o.id, true);
          },
        }, rightLabel)
      : React.createElement("button", {
          type: "button",
          onClick: function () {
            try { if (typeof p.onConfirm === "function") p.onConfirm(); } catch (_) {}
            __oxClose(o.id, true);
          },
        }, rightLabel);

    ctaNode = React.createElement(BS.DoubleCTA, { leftButton: leftBtn, rightButton: rightBtn });
  }

  return React.createElement(
    BS,
    {
      key: "ox-bs-" + o.id,
      open: open,
      onClose: function () { __oxClose(o.id, false); },
      onExited: function () { __oxRemove(o.id); },
      header: headerNode,
      headerDescription: descNode,
      cta: ctaNode,
      className: p.className,
      dimmerClassName: p.dimmerClassName,
      disableDimmer: !!p.disableDimmer,
      hasTextField: !!p.hasTextField,
      maxHeight: p.maxHeight,
      expandedMaxHeight: p.expandedMaxHeight,
      expandBottomSheet: !!p.expandBottomSheet,
      expandBottomSheetWhenScroll: !!p.expandBottomSheetWhenScroll,
      ctaContentGap: p.ctaContentGap,
      ariaLabelledBy: p.ariaLabelledBy,
      ariaDescribedBy: p.ariaDescribedBy,
      onEntered: p.onEntered,
      onExpanded: p.onExpanded,
      onDimmerClick: p.onDimmerClick,
      onHandlerTouchStart: p.onHandlerTouchStart,
      onHandlerTouchEnd: p.onHandlerTouchEnd,
      UNSAFE_disableFocusLock: !!p.UNSAFE_disableFocusLock,
      UNSAFE_ignoreDimmerClick: !!p.UNSAFE_ignoreDimmerClick,
      UNSAFE_ignoreBackEvent: !!p.UNSAFE_ignoreBackEvent,
      a11yIncludeHeaderInScroll: p.a11yIncludeHeaderInScroll !== false,
      disableChildrenDragging: !!p.disableChildrenDragging,
    },
    p.children
  );
}

/* ----------------------------------------------------------------------------
 * OverlayRoot 마운트 — 모듈 최초 훅 호출 시 1회만 document.body 에 마운트
 * -------------------------------------------------------------------------- */
let __overlayRootMounted = false;
function __oxEnsureMount() {
  if (__overlayRootMounted) return;
  if (typeof document === "undefined") return;

  // 동일 id 로 여러 번 mount 되는 것 방지
  let host = document.getElementById("tds-overlay-root");
  if (!host) {
    host = document.createElement("div");
    host.id = "tds-overlay-root";
    // Portal 들이 각자 document.body 로 붙으므로 이 host 는 그냥 root 마운트 지점
    document.body.appendChild(host);
  }

  try {
    if (typeof ReactDOM !== "undefined" && typeof ReactDOM.createRoot === "function") {
      const root = ReactDOM.createRoot(host);
      root.render(React.createElement(OverlayRoot));
    } else if (typeof ReactDOM !== "undefined" && typeof ReactDOM.render === "function") {
      // React 17 이하 fallback
      ReactDOM.render(React.createElement(OverlayRoot), host);
    } else {
      if (window.console && console.warn) console.warn("[Overlay] ReactDOM 미로드 — OverlayRoot 마운트 실패");
      return;
    }
    __overlayRootMounted = true;
  } catch (e) {
    if (window.console && console.warn) console.warn("[Overlay] OverlayRoot 마운트 실패", e);
  }
}

/* ----------------------------------------------------------------------------
 * useDialog
 * -------------------------------------------------------------------------- */
function useDialog() {
  __oxEnsureMount();

  const openAlert = __oxUseCallback(function (options) {
    const opts = options || {};
    return __oxPush("alert", opts, null);
  }, []);

  const openConfirm = __oxUseCallback(function (options) {
    const opts = options || {};
    return __oxPush("confirm", opts, null);
  }, []);

  const openAsyncConfirm = __oxUseCallback(function (options) {
    const opts = options || {};
    return new Promise(function (resolve) {
      __oxPush("confirm", opts, resolve);
    });
  }, []);

  return __oxUseMemo(function () {
    return {
      openAlert: openAlert,
      openConfirm: openConfirm,
      openAsyncConfirm: openAsyncConfirm,
    };
  }, [openAlert, openConfirm, openAsyncConfirm]);
}

/* ----------------------------------------------------------------------------
 * useToast
 * -------------------------------------------------------------------------- */
function useToast() {
  __oxEnsureMount();

  const openToast = __oxUseCallback(function (options) {
    const opts = options || {};
    // 기본 duration — 앱인토스에선 5000, 웹에선 3000 (button 있을 때 Toast 컴포넌트 자체에서 5000 사용)
    if (opts.duration == null) {
      let isInToss = false;
      try {
        if (typeof window.TossAdapter === "object" && typeof window.TossAdapter.isInToss === "function") {
          isInToss = !!window.TossAdapter.isInToss();
        }
      } catch (_) {}
      // button 이 있으면 Toast 컴포넌트 내부에서 5000 으로 올림. 여기선 button 없을 때만 환경별 기본.
      if (!opts.button) {
        opts = Object.assign({}, opts, { duration: isInToss ? 5000 : 3000 });
      }
    }
    return __oxPush("toast", opts, null);
  }, []);

  const closeToast = __oxUseCallback(function (id) {
    if (id != null) {
      __oxClose(id, false);
      return;
    }
    // id 생략 시 — 가장 최근 toast 만 닫음
    const overlays = __overlayState.overlays;
    for (let i = overlays.length - 1; i >= 0; i -= 1) {
      if (overlays[i].kind === "toast" && !overlays[i].closing) {
        __oxClose(overlays[i].id, false);
        break;
      }
    }
  }, []);

  return __oxUseMemo(function () {
    return { openToast: openToast, closeToast: closeToast };
  }, [openToast, closeToast]);
}

/* ----------------------------------------------------------------------------
 * useBottomSheet
 * -------------------------------------------------------------------------- */
function useBottomSheet() {
  __oxEnsureMount();

  const open = __oxUseCallback(function (options) {
    const opts = options || {};
    return __oxPush("bottomsheet", opts, null);
  }, []);

  const close = __oxUseCallback(function (id) {
    if (id != null) {
      __oxClose(id, false);
      return;
    }
    const overlays = __overlayState.overlays;
    for (let i = overlays.length - 1; i >= 0; i -= 1) {
      if (overlays[i].kind === "bottomsheet" && !overlays[i].closing) {
        __oxClose(overlays[i].id, false);
        break;
      }
    }
  }, []);

  const openOneButtonSheet = __oxUseCallback(function (options) {
    const opts = Object.assign({}, options || {}, { __variant: "one-button" });
    return __oxPush("bottomsheet", opts, null);
  }, []);

  const openTwoButtonSheet = __oxUseCallback(function (options) {
    const opts = Object.assign({}, options || {}, { __variant: "two-button" });
    return __oxPush("bottomsheet", opts, null);
  }, []);

  const openAsyncTwoButtonSheet = __oxUseCallback(function (options) {
    const opts = Object.assign({}, options || {}, { __variant: "two-button" });
    return new Promise(function (resolve) {
      __oxPush("bottomsheet", opts, resolve);
    });
  }, []);

  return __oxUseMemo(function () {
    return {
      open: open,
      close: close,
      openOneButtonSheet: openOneButtonSheet,
      openTwoButtonSheet: openTwoButtonSheet,
      openAsyncTwoButtonSheet: openAsyncTwoButtonSheet,
    };
  }, [open, close, openOneButtonSheet, openTwoButtonSheet, openAsyncTwoButtonSheet]);
}

/* ----------------------------------------------------------------------------
 * window 등록
 * -------------------------------------------------------------------------- */
window.useDialog = useDialog;
window.useToast = useToast;
window.useBottomSheet = useBottomSheet;
window.OverlayRoot = OverlayRoot;

// 디버그/테스트용 노출
window.__tdsOverlay = {
  state: __overlayState,
  push: __oxPush,
  close: __oxClose,
  remove: __oxRemove,
  ensureMount: __oxEnsureMount,
};

Object.assign(window, { useDialog, useToast, useBottomSheet, OverlayRoot });
