/* ============================================================================
 * 서명전에 — TDS BottomCTA / FixedBottomCTA 컴포넌트  (Task #159)
 *
 *  · TDS Mobile 2026-03 BottomCTA 스펙. 화면 하단의 Call-to-Action 영역을
 *    표준화한 컴포넌트. "Single"(버튼 1개)·"Double"(버튼 2개)·`fixed` 포지션·
 *    스크롤 시 숨김·키보드 위 띄움·세이프에어리어 패딩 등 화면 하단 CTA 가
 *    다뤄야 할 거의 모든 케이스를 커버해요.
 *
 *  · 두 가지 사용 방식 (스펙상 완전 동일):
 *      <BottomCTA fixed> ↔ <FixedBottomCTA>
 *      <BottomCTA.Single> ↔ <BottomCTASingle>
 *      <FixedBottomCTA.Double> ↔ <FixedBottomCTA><DoubleCTA …/></FixedBottomCTA>
 *
 *  · 구조:
 *      BottomCTA              ─ 루트 컨테이너 (Single/Double 자식 형태에 맞춰 dispatch)
 *      ├── BottomCTA.Single   ─ 풀폭 버튼 1개 (children = Button)
 *      ├── BottomCTA.Double   ─ leftButton + rightButton 2개
 *      ├── BottomCTA.Button   ─ 편의 alias — display="block" + size="xlarge" 기본 Button
 *      └── BottomCTA.Spacer   ─ takeSpace 모드에서 바깥에서 직접 고정 높이 채우고 싶을 때
 *      FixedBottomCTA         ─ <BottomCTA fixed={true} …/> 의 베이크된 별칭
 *      ├── FixedBottomCTA.Single
 *      └── FixedBottomCTA.Double
 *
 *  · 핵심 props (Single/Double 공통):
 *      fixed                  boolean. true 면 viewport 하단 fixed 포지션.
 *                             FixedBottomCTA 는 항상 true.
 *      show                   boolean (기본 true). false 면 화면에서 숨김 (트랜지션 적용).
 *      showAfterDelay         number(ms) — 0/미지정이면 즉시 등장. 양수면 그 시간 후 fade/slide-in.
 *      hideOnScroll           boolean. true 면 사용자가 위로 스크롤 시 숨김 / 아래로 시 다시 등장.
 *      hideOnScrollDistanceThreshold  number(px, 기본 8). 스크롤 누적이 이 값 넘어야 숨김 토글.
 *      hasSafeAreaPadding     boolean (기본 true). webview 의 하단 세이프에어리어 만큼 패딩 추가.
 *      hasPaddingBottom       boolean (기본 true). 기본 20 px 하단 여백.
 *                             false 면 위 두 패딩 모두 0.
 *      takeSpace              boolean. 부모 흐름에서 본인 높이만큼 자리 차지(스페이서 자동 삽입).
 *                             fixed=true 인데 화면 하단 컨텐츠가 가려지면 안 될 때.
 *      background             "default"(기본) | "none". default 는 위쪽 페이드(투명→흰색) 그라데이션.
 *      animation              "fade"(기본) | "scale" | "slide". 등장/숨김 트랜지션.
 *      topAccessory           ReactNode. 버튼 위에 오는 보조 영역 (예: 동의 체크박스 줄).
 *      bottomAccessory        ReactNode. 버튼 아래에 오는 보조 영역 (예: 하단 안내 문구).
 *      containerStyle         CSSProperties. 루트 컨테이너 인라인 스타일 패스스루.
 *      containerRef           Ref. 루트 컨테이너 DOM ref.
 *      className              string. 루트 클래스 추가.
 *      onShowChange(show)     boolean 콜백 — 트랜지션 종료 시 show 의 최종값.
 *
 *  · Single 전용:
 *      children               필수 — 단일 버튼 (또는 임의 노드). 풀폭 자동 적용.
 *      fixedAboveKeyboard     boolean. 가상 키보드 등장 시 키보드 바로 위로 따라 올라옴.
 *                             window.visualViewport API 사용. fixed=true 와 같이 써야 의미 있음.
 *
 *  · Double 전용:
 *      leftButton             필수 — 왼쪽 버튼 (보통 보조 액션 — variant="weak").
 *      rightButton            필수 — 오른쪽 버튼 (보통 메인 액션 — variant="fill").
 *      ratio                  "1:1"(기본) | "1:2" | "2:1" | "1:3" | "3:1"
 *                             좌/우 버튼의 가로 비율.
 *
 *  · CSS 변수 (외부 오버라이드 가능):
 *      --bottom-cta-padding-bottom  = max(--toss-safe-area-bottom, env(safe-area-inset-bottom), 20px)
 *                                     (hasSafeAreaPadding=false 면 0, hasPaddingBottom=false 도 0)
 *      --bottom-cta-padding-x       = 16px (좌우)
 *      --bottom-cta-padding-top     = 12px
 *      --bottom-cta-fade-h          = 24px (background="default" 의 위쪽 페이드 높이)
 *      --bottom-cta-z               = 50 (fixed 일 때만 의미)
 *
 *  · 모든 색·간격은 var(--…) 토큰 참조. 하드코딩 금지.
 *  · prefers-reduced-motion 시 모든 transition/transform OFF.
 *
 *  · 다른 컴포넌트와의 역할 구분:
 *      - vs Top.LowerCTA(#156) : Top 영역에 들러붙어 스크롤 같이 따라가는 헤더 하단 액션
 *        은 Top.LowerCTA. 페이지 하단·뷰포트 하단에 고정/임시 고정되는 페이지 액션은 BottomCTA.
 *      - vs BottomSheet.CTA(#125) : BottomSheet 내부 닫기/확인 액션은 BottomSheet.CTA·DoubleCTA.
 *        화면 자체의 액션은 BottomCTA / FixedBottomCTA.
 *      - vs Button(#127) 단독 : Button 만 단독으로 두면 위치/패딩/세이프에어리어/페이드를
 *        직접 잡아줘야 함. 화면 하단 메인 액션이라면 BottomCTA 로 감싸기.
 * ========================================================================== */

(function ensureAdaptiveForBCTA() {
  if (typeof window === "undefined") return;
  if (window.adaptive) return;
  // AgreementV4 / Asset 와 동일한 토큰 매핑. 단독 로드 대비 가드.
  var hues = ["red", "orange", "yellow", "green", "blue", "purple", "pink", "grey"];
  var steps = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
  var out = { white: "#ffffff", black: "#000000", transparent: "transparent" };
  hues.forEach(function (h) {
    steps.forEach(function (s) {
      out[h + s] = "var(--tds-" + h + "-" + s + ")";
    });
  });
  window.adaptive = out;
})();

// ─── 상수 ─────────────────────────────────────────────────────────────
const __BCTA_ANIMATIONS = ["fade", "scale", "slide"];
const __BCTA_BACKGROUNDS = ["default", "none"];
const __BCTA_RATIOS = {
  "1:1": "1fr 1fr",
  "1:2": "1fr 2fr",
  "2:1": "2fr 1fr",
  "1:3": "1fr 3fr",
  "3:1": "3fr 1fr",
};

function __bctaWarn(msg) {
  if (typeof console !== "undefined" && console.warn) console.warn("[BottomCTA] " + msg);
}

function __bctaEnum(value, allowed, fallback, name) {
  if (value == null) return fallback;
  if (allowed.indexOf(value) === -1) {
    __bctaWarn(name + ': "' + value + '" 은 지원하지 않아요. 가능한 값: ' + allowed.join(", ") + ". \"" + fallback + '" 로 폴백.');
    return fallback;
  }
  return value;
}

// ─── 훅: 등장 지연 (showAfterDelay) ──────────────────────────────────
function __bctaUseEntrance(show, showAfterDelay) {
  const [entered, setEntered] = React.useState(() => {
    return show && (!showAfterDelay || showAfterDelay <= 0);
  });
  React.useEffect(() => {
    if (!show) {
      setEntered(false);
      return;
    }
    if (!showAfterDelay || showAfterDelay <= 0) {
      // requestAnimationFrame 으로 한 틱 미루어야 트랜지션이 적용돼요.
      let raf = requestAnimationFrame(() => setEntered(true));
      return () => cancelAnimationFrame(raf);
    }
    const id = setTimeout(() => setEntered(true), showAfterDelay);
    return () => clearTimeout(id);
  }, [show, showAfterDelay]);
  return entered;
}

// ─── 훅: 스크롤 시 숨김 (hideOnScroll) ───────────────────────────────
function __bctaUseScrollHide(enabled, threshold) {
  const [hidden, setHidden] = React.useState(false);
  React.useEffect(() => {
    if (!enabled) {
      setHidden(false);
      return;
    }
    let lastY = window.pageYOffset || document.documentElement.scrollTop || 0;
    let acc = 0;
    const onScroll = () => {
      const y = window.pageYOffset || document.documentElement.scrollTop || 0;
      const dy = y - lastY;
      lastY = y;
      // 같은 방향으로 누적, 방향 바뀌면 리셋.
      if ((dy > 0 && acc < 0) || (dy < 0 && acc > 0)) acc = 0;
      acc += dy;
      const limit = typeof threshold === "number" && threshold >= 0 ? threshold : 8;
      if (acc > limit) {
        setHidden(true);
        acc = 0;
      } else if (acc < -limit) {
        setHidden(false);
        acc = 0;
      }
      // 페이지 최상단/최하단 부근에서는 항상 보이도록.
      if (y <= 0) setHidden(false);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [enabled, threshold]);
  return hidden;
}

// ─── 훅: 컨테이너 높이 측정 (takeSpace) ──────────────────────────────
function __bctaUseHeight(targetRef, enabled) {
  const [h, setH] = React.useState(0);
  React.useEffect(() => {
    if (!enabled) return;
    const el = targetRef && targetRef.current;
    if (!el) return;
    const measure = () => {
      const rect = el.getBoundingClientRect();
      setH(Math.round(rect.height));
    };
    measure();
    let ro = null;
    if (typeof ResizeObserver !== "undefined") {
      ro = new ResizeObserver(measure);
      ro.observe(el);
    } else {
      window.addEventListener("resize", measure);
    }
    return () => {
      if (ro) ro.disconnect();
      else window.removeEventListener("resize", measure);
    };
  }, [targetRef, enabled]);
  return h;
}

// ─── 훅: 키보드 위 따라가기 (fixedAboveKeyboard) ─────────────────────
function __bctaUseKeyboardOffset(enabled) {
  const [bottom, setBottom] = React.useState(0);
  React.useEffect(() => {
    if (!enabled) {
      setBottom(0);
      return;
    }
    const vv = typeof window !== "undefined" ? window.visualViewport : null;
    if (!vv) return; // 비지원 환경 (visualViewport API 없음)
    const onChange = () => {
      // visualViewport.height + offsetTop 가 화면에서 보이는 영역의 바닥.
      // window.innerHeight 와의 차이가 키보드가 차지한 높이.
      const innerH = window.innerHeight || document.documentElement.clientHeight;
      const visibleBottom = vv.height + vv.offsetTop;
      const occluded = Math.max(0, innerH - visibleBottom);
      setBottom(occluded);
    };
    onChange();
    vv.addEventListener("resize", onChange);
    vv.addEventListener("scroll", onChange);
    return () => {
      vv.removeEventListener("resize", onChange);
      vv.removeEventListener("scroll", onChange);
    };
  }, [enabled]);
  return bottom;
}

// ─── 공통 컨테이너 props 빌더 ────────────────────────────────────────
function __bctaBuildClasses(opts) {
  return [
    "tds-bcta",
    "tds-bcta--" + opts.kind,                  // single | double
    "tds-bcta--bg-" + opts.background,         // default | none
    "tds-bcta--anim-" + opts.animation,        // fade | scale | slide
    opts.fixed && "tds-bcta--fixed",
    opts.entered ? "is-entered" : "is-pre",
    opts.hidden && "is-hidden",
    !opts.show && "is-hide-requested",
    opts.hasSafeAreaPadding === false && "tds-bcta--no-safe",
    opts.hasPaddingBottom === false && "tds-bcta--no-pb",
    opts.className,
  ]
    .filter(Boolean)
    .join(" ");
}

// ─── 공통 컨테이너 ───────────────────────────────────────────────────
function __BCTAContainer({
  kind,
  fixed,
  show,
  showAfterDelay,
  hideOnScroll,
  hideOnScrollDistanceThreshold,
  hasSafeAreaPadding,
  hasPaddingBottom,
  takeSpace,
  background,
  animation,
  topAccessory,
  bottomAccessory,
  containerStyle,
  containerRef,
  className,
  onShowChange,
  fixedAboveKeyboard, // single only — 외부에서는 0/false 로 보장
  children,
}) {
  const innerRef = React.useRef(null);
  // containerRef 는 ref 객체({current}) / callback ref 둘 다 지원.
  React.useEffect(() => {
    if (!containerRef) return;
    if (typeof containerRef === "function") {
      containerRef(innerRef.current);
      return () => containerRef(null);
    }
    if (typeof containerRef === "object") {
      containerRef.current = innerRef.current;
      return () => {
        // 언마운트 시 외부 ref 가 stale 한 노드를 가리키지 않게 정리.
        if (containerRef && containerRef.current === innerRef.current) {
          containerRef.current = null;
        }
      };
    }
  }, [containerRef]);

  const safeBg = __bctaEnum(background, __BCTA_BACKGROUNDS, "default", "background");
  const safeAnim = __bctaEnum(animation, __BCTA_ANIMATIONS, "fade", "animation");

  const entered = __bctaUseEntrance(!!show, showAfterDelay);
  const scrollHidden = __bctaUseScrollHide(!!hideOnScroll && !!show, hideOnScrollDistanceThreshold);
  const hidden = !show || scrollHidden;
  const measuredH = __bctaUseHeight(innerRef, !!takeSpace);
  const kbOffset = __bctaUseKeyboardOffset(!!fixedAboveKeyboard && !!fixed);

  // show prop 변경 시 트랜지션 종료 후 onShowChange 알림.
  React.useEffect(() => {
    if (!onShowChange) return;
    const el = innerRef.current;
    if (!el) return;
    const onEnd = (e) => {
      if (e.target !== el) return;
      onShowChange(!!show && !scrollHidden);
    };
    el.addEventListener("transitionend", onEnd);
    return () => el.removeEventListener("transitionend", onEnd);
  }, [show, scrollHidden, onShowChange]);

  const cls = __bctaBuildClasses({
    kind: kind,
    fixed: !!fixed,
    show: !!show,
    entered: entered,
    hidden: hidden,
    background: safeBg,
    animation: safeAnim,
    hasSafeAreaPadding: hasSafeAreaPadding,
    hasPaddingBottom: hasPaddingBottom,
    className: className || "",
  });

  // 키보드 오프셋은 fixed 일 때만 의미 있음 (translateY 로 위로 밀어 올리기).
  const dynStyle = {};
  if (fixed && kbOffset > 0) {
    dynStyle["--bottom-cta-keyboard-offset"] = kbOffset + "px";
  }
  const mergedStyle = Object.assign({}, dynStyle, containerStyle || {});

  const node = (
    <React.Fragment>
      <div
        ref={innerRef}
        className={cls}
        style={mergedStyle}
        role="region"
        aria-label="화면 하단 액션"
        aria-hidden={!show ? "true" : undefined}
      >
        {topAccessory ? <div className="tds-bcta__top-accessory">{topAccessory}</div> : null}
        <div className="tds-bcta__buttons">{children}</div>
        {bottomAccessory ? <div className="tds-bcta__bottom-accessory">{bottomAccessory}</div> : null}
      </div>
      {takeSpace && fixed ? (
        <div
          className="tds-bcta__spacer"
          aria-hidden="true"
          style={{ height: measuredH ? measuredH + "px" : undefined }}
        />
      ) : null}
    </React.Fragment>
  );

  return node;
}

// ─── BottomCTA.Single ────────────────────────────────────────────────
function BottomCTASingle(props) {
  const {
    fixed = false,
    show = true,
    showAfterDelay = 0,
    hideOnScroll = false,
    hideOnScrollDistanceThreshold = 8,
    hasSafeAreaPadding = true,
    hasPaddingBottom = true,
    takeSpace = false,
    background = "default",
    animation = "fade",
    fixedAboveKeyboard = false,
    topAccessory,
    bottomAccessory,
    containerStyle,
    containerRef,
    className,
    onShowChange,
    children,
    ...rest
  } = props;
  return (
    <__BCTAContainer
      kind="single"
      fixed={fixed}
      show={show}
      showAfterDelay={showAfterDelay}
      hideOnScroll={hideOnScroll}
      hideOnScrollDistanceThreshold={hideOnScrollDistanceThreshold}
      hasSafeAreaPadding={hasSafeAreaPadding}
      hasPaddingBottom={hasPaddingBottom}
      takeSpace={takeSpace}
      background={background}
      animation={animation}
      topAccessory={topAccessory}
      bottomAccessory={bottomAccessory}
      containerStyle={containerStyle}
      containerRef={containerRef}
      className={className}
      onShowChange={onShowChange}
      fixedAboveKeyboard={fixedAboveKeyboard}
      {...rest}
    >
      <div className="tds-bcta__single-slot">{children}</div>
    </__BCTAContainer>
  );
}

// ─── BottomCTA.Double ────────────────────────────────────────────────
function BottomCTADouble(props) {
  const {
    fixed = false,
    show = true,
    showAfterDelay = 0,
    hideOnScroll = false,
    hideOnScrollDistanceThreshold = 8,
    hasSafeAreaPadding = true,
    hasPaddingBottom = true,
    takeSpace = false,
    background = "default",
    animation = "fade",
    leftButton,
    rightButton,
    ratio = "1:1",
    topAccessory,
    bottomAccessory,
    containerStyle,
    containerRef,
    className,
    onShowChange,
    ...rest
  } = props;
  if (!leftButton || !rightButton) {
    __bctaWarn("Double 은 leftButton 과 rightButton 모두 필요해요.");
  }
  const safeRatio = __BCTA_RATIOS[ratio] || __BCTA_RATIOS["1:1"];
  if (!__BCTA_RATIOS[ratio]) __bctaWarn('ratio "' + ratio + '" 은 지원하지 않아요. "1:1" 로 폴백.');
  const slotsStyle = { gridTemplateColumns: safeRatio };
  return (
    <__BCTAContainer
      kind="double"
      fixed={fixed}
      show={show}
      showAfterDelay={showAfterDelay}
      hideOnScroll={hideOnScroll}
      hideOnScrollDistanceThreshold={hideOnScrollDistanceThreshold}
      hasSafeAreaPadding={hasSafeAreaPadding}
      hasPaddingBottom={hasPaddingBottom}
      takeSpace={takeSpace}
      background={background}
      animation={animation}
      topAccessory={topAccessory}
      bottomAccessory={bottomAccessory}
      containerStyle={containerStyle}
      containerRef={containerRef}
      className={className}
      onShowChange={onShowChange}
      {...rest}
    >
      <div className="tds-bcta__double-slot" style={slotsStyle}>
        <div className="tds-bcta__double-left">{leftButton}</div>
        <div className="tds-bcta__double-right">{rightButton}</div>
      </div>
    </__BCTAContainer>
  );
}

// ─── BottomCTA 루트 — children 형태로 dispatch ───────────────────────
// `<BottomCTA><Button>…</Button></BottomCTA>` 처럼 children 만 주면 Single 처럼 동작.
// 그 외에는 prop 으로 명시한 변종 (Single/Double) 을 직접 사용하도록 권장.
function BottomCTA(props) {
  // Double 시그니처(leftButton + rightButton) 가 들어오면 Double 로.
  if (props.leftButton || props.rightButton) {
    return <BottomCTADouble {...props} />;
  }
  return <BottomCTASingle {...props} />;
}

// ─── FixedBottomCTA — fixed=true 베이크 ──────────────────────────────
function FixedBottomCTA(props) {
  return <BottomCTA {...props} fixed={true} />;
}
function FixedBottomCTASingle(props) {
  return <BottomCTASingle {...props} fixed={true} />;
}
function FixedBottomCTADouble(props) {
  return <BottomCTADouble {...props} fixed={true} />;
}

// ─── BottomCTA.Button — 편의 alias ───────────────────────────────────
// display="block" + size="xlarge" 기본으로 Button(#127) 호출.
function BottomCTAButton(props) {
  const Btn = window.Button;
  if (!Btn) {
    __bctaWarn("Button(#127) 컴포넌트가 로드되지 않았어요. 일반 button 으로 폴백합니다.");
    const { children, className = "", ...rest } = props;
    return (
      <button
        className={"tds-bcta__fallback-btn " + className}
        type={rest.type || "button"}
        {...rest}
      >
        {children}
      </button>
    );
  }
  const { display = "block", size = "xlarge", ...rest } = props;
  return <Btn display={display} size={size} {...rest} />;
}

// ─── BottomCTA.Spacer — 외부 직접 사용용 (takeSpace 와 별개) ─────────
// "지금 화면에 BottomCTA 자리만큼 비워둬" 가 필요하지만 BottomCTA 자체가
// 다른 페이지에 마운트된 상황 (포털 등) 에 대비.
function BottomCTASpacer({ height = 96, style, className = "", ...rest }) {
  const h = typeof height === "number" ? height + "px" : height;
  const cls = ("tds-bcta__spacer " + className).trim();
  return (
    <div
      className={cls}
      aria-hidden="true"
      style={Object.assign({ height: h }, style || {})}
      {...rest}
    />
  );
}

// ─── 정적 프로퍼티 부착 ──────────────────────────────────────────────
BottomCTA.Single = BottomCTASingle;
BottomCTA.Double = BottomCTADouble;
BottomCTA.Button = BottomCTAButton;
BottomCTA.Spacer = BottomCTASpacer;

FixedBottomCTA.Single = FixedBottomCTASingle;
FixedBottomCTA.Double = FixedBottomCTADouble;
FixedBottomCTA.Button = BottomCTAButton;
FixedBottomCTA.Spacer = BottomCTASpacer;

// ─── 글로벌 등록 ─────────────────────────────────────────────────────
window.BottomCTA = BottomCTA;
window.FixedBottomCTA = FixedBottomCTA;
window.BottomCTASingle = BottomCTASingle;
window.BottomCTADouble = BottomCTADouble;
Object.assign(window, {
  BottomCTA,
  FixedBottomCTA,
  BottomCTASingle,
  BottomCTADouble,
});
