/* ============================================================================
 * 서명전에 — TDS Tooltip 컴포넌트
 *  · 트리거(아이콘/버튼 등) 위 또는 아래에 떠올라 짧은 보조 정보를 보여줘요.
 *    Toast(#154) 와 다른 점: 트리거에 종속(트리거를 가리키는 화살표)이고,
 *    호버/포커스/클릭 같은 사용자 의도가 있어야 열려요.
 *
 *  · 단일 컴포넌트 — children 으로 트리거 1개를 감싸요. React.cloneElement 로
 *    이벤트(onMouseEnter/Leave/Focus/Blur)와 ref 를 트리거에 합성해 부착해요.
 *
 *  · 라이프사이클 (controlled / uncontrolled 둘 다):
 *      open 명시 → controlled (외부에서 관리, onOpenChange 로 신호)
 *      open 누락 → uncontrolled (defaultOpen=false / openOnHover / openOnFocus
 *                  / dismissible 로 내부에서 관리)
 *      mount → 위치 계산(트리거 rect + anchorPositionByRatio + offset) →
 *        autoFlip 시 viewport 검사 후 placement 뒤집기 → 16ms 후 visible →
 *        퇴장 시 visible 해제 → 모션 끝나면 unmount.
 *
 *  · 시각:
 *      배경 grey-900 / 흰 텍스트 / 8px 라운드 / shadow-elevation-medium.
 *      messageAlign 은 메시지 텍스트 정렬 (style.width 가 명시돼야 의미 있음).
 *      화살표는 tooltip 과 같은 색 (currentColor 상속) 인라인 SVG.
 *      anchorPositionByRatio 는 화살표의 tooltip 내 가로 위치 비율 (0~1).
 *        0.5 → 가운데. 0.1 → 좌측. 0.9 → 우측.
 *      clipToEnd: none / left(왼쪽 절반만 남김) / right(오른쪽 절반만 남김).
 *
 *  · size → padding / font / arrow:
 *      small : 6px 8px  / 12/16  / 5×4
 *      medium: 8px 10px / 13/18  / 6×5
 *      large : 10px 12px/ 14/20  / 7×6
 *      default offset = arrow.height + 2px (size 별 자동).
 *
 *  · 위치 계산 — Portal(body) 마운트 + 트리거 getBoundingClientRect:
 *      strategy="absolute": top/left = rect + window.scrollX/Y
 *      strategy="fixed":    top/left = rect 그대로 (viewport 좌표)
 *      anchor X = rect.left + rect.width/2  (트리거 가로 중앙)
 *      tooltip.left = anchor X - tooltip.width * anchorPositionByRatio
 *      placement=bottom: tooltip.top = rect.bottom + offset
 *      placement=top   : tooltip.top = rect.top - tooltip.height - offset
 *      autoFlip: bottom 인데 아래로 넘치면 top, top 인데 위로 넘치면 bottom.
 *      좌우 viewport 8px 마진 안으로 clamp.
 *
 *  · 접근성:
 *      tooltip 본체에 role="tooltip" + id 부여 → 트리거에 aria-describedby 자동.
 *      openOnFocus 일 때 키보드 포커스로 열림 (포커스 사용자 접근 가능).
 *      Escape 닫기 (dismissible).
 *      visible/leaving 클래스만 토글 — 트리거 콘텐츠는 건드리지 않음.
 *
 *  · 모션:
 *      motionVariant=weak  : opacity 120ms + translateY 4px
 *      motionVariant=strong: opacity 200ms + translateY 8px + scale(0.96→1)
 *      prefers-reduced-motion 시 transform 없이 fade 만 80ms.
 * ========================================================================== */

const {
  useState: __ttUseState,
  useEffect: __ttUseEffect,
  useRef: __ttUseRef,
  useMemo: __ttUseMemo,
  useCallback: __ttUseCallback,
  useId: __ttUseIdMaybe,
} = React;

const __TOOLTIP_SIZES = ["small", "medium", "large"];
const __TOOLTIP_PLACEMENTS = ["top", "bottom"];
const __TOOLTIP_ALIGNS = ["left", "center", "right"];
const __TOOLTIP_VARIANTS = ["weak", "strong"];
const __TOOLTIP_CLIPS = ["none", "left", "right"];
const __TOOLTIP_STRATEGIES = ["absolute", "fixed"];

// size 별 화살표 / 기본 offset
const __TOOLTIP_ARROW = {
  small:  { w: 10, h: 5, defaultOffset: 7  },
  medium: { w: 12, h: 6, defaultOffset: 8  },
  large:  { w: 14, h: 7, defaultOffset: 10 },
};

// useId 폴백 (React 17 환경 대비)
let __tooltipSeq = 0;
function __ttGenId() {
  __tooltipSeq += 1;
  return `tds-tooltip-${__tooltipSeq}`;
}

function Tooltip({
  // 컨텐츠
  message,
  messageAlign = "left",
  children,
  // 시각
  size = "medium",
  placement = "bottom",
  motionVariant = "weak",
  clipToEnd = "none",
  // 상태
  open,
  defaultOpen = false,
  onOpenChange,
  openOnHover = false,
  openOnFocus = false,
  dismissible = false,
  // 위치
  offset,
  anchorPositionByRatio = 0.5,
  autoFlip = false,
  strategy = "absolute",
  // 외부 스타일 (메시지 박스에 붙음)
  className = "",
  style,
  id,
  // 접근성
  "aria-label": ariaLabel,
  ...rest
}) {
  // ----- props 검증 -----
  if (!__TOOLTIP_SIZES.includes(size)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] size 는 ${__TOOLTIP_SIZES.join(" | ")} 만. 받은 값: ${size}. "medium" 폴백.`);
    }
    size = "medium";
  }
  if (!__TOOLTIP_PLACEMENTS.includes(placement)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] placement 는 "top" | "bottom" 만. 받은 값: ${placement}. "bottom" 폴백.`);
    }
    placement = "bottom";
  }
  if (!__TOOLTIP_ALIGNS.includes(messageAlign)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] messageAlign 은 "left" | "center" | "right" 만. 받은 값: ${messageAlign}. "left" 폴백.`);
    }
    messageAlign = "left";
  }
  if (!__TOOLTIP_VARIANTS.includes(motionVariant)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] motionVariant 는 "weak" | "strong" 만. 받은 값: ${motionVariant}. "weak" 폴백.`);
    }
    motionVariant = "weak";
  }
  if (!__TOOLTIP_CLIPS.includes(clipToEnd)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] clipToEnd 는 "none" | "left" | "right" 만. 받은 값: ${clipToEnd}. "none" 폴백.`);
    }
    clipToEnd = "none";
  }
  if (!__TOOLTIP_STRATEGIES.includes(strategy)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] strategy 는 "absolute" | "fixed" 만. 받은 값: ${strategy}. "absolute" 폴백.`);
    }
    strategy = "absolute";
  }
  if (typeof anchorPositionByRatio !== "number" || !isFinite(anchorPositionByRatio)) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] anchorPositionByRatio 는 number(0~1) 여야 해요. 받은 값: ${anchorPositionByRatio}. 0.5 폴백.`);
    }
    anchorPositionByRatio = 0.5;
  } else if (anchorPositionByRatio < 0 || anchorPositionByRatio > 1) {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] anchorPositionByRatio 는 [0, 1] 범위. 받은 값: ${anchorPositionByRatio}. 클램프함.`);
    }
    anchorPositionByRatio = Math.max(0, Math.min(1, anchorPositionByRatio));
  }
  if (open !== undefined && open !== null && typeof open !== "boolean") {
    if (window.console && console.warn) {
      console.warn(`[Tooltip] open 은 boolean 이어야 해요. 받은 값: ${open}. 무시.`);
    }
    open = undefined;
  }
  if (children == null || (Array.isArray(children) && children.length === 0)) {
    if (window.console && console.warn) {
      console.warn("[Tooltip] children(트리거)이 필요해요.");
    }
  }

  const isControlled = typeof open === "boolean";

  // ----- state -----
  const [innerOpen, setInnerOpen] = __ttUseState(!!defaultOpen);
  const effectiveOpen = isControlled ? open : innerOpen;

  // 위치 / 크기
  const [pos, setPos] = __ttUseState({ top: 0, left: 0, arrowLeft: 0, flipped: false });
  const [mounted, setMounted] = __ttUseState(effectiveOpen);
  const [visible, setVisible] = __ttUseState(false);

  // refs
  const triggerRef = __ttUseRef(null);
  const tooltipRef = __ttUseRef(null);
  const exitTimerRef = __ttUseRef(null);
  const enterRafRef = __ttUseRef(null);
  const repositionRafRef = __ttUseRef(null);

  // id (aria-describedby 용)
  const idAuto = __ttUseMemo(function () {
    return id || __ttGenId();
  }, [id]);

  // ----- open 변경 dispatch -----
  const requestSetOpen = __ttUseCallback(function (next) {
    if (typeof onOpenChange === "function") onOpenChange(next);
    if (!isControlled) setInnerOpen(next);
  }, [isControlled, onOpenChange]);

  // ----- 위치 계산 -----
  const computePosition = __ttUseCallback(function () {
    const trig = triggerRef.current;
    const tip = tooltipRef.current;
    if (!trig || !tip) return;

    const rect = trig.getBoundingClientRect();
    const tipRect = tip.getBoundingClientRect();

    const arrowMeta = __TOOLTIP_ARROW[size] || __TOOLTIP_ARROW.medium;
    const effOffset = (typeof offset === "number" && isFinite(offset)) ? offset : arrowMeta.defaultOffset;

    // 트리거 가로 중심
    const anchorX = rect.left + rect.width / 2;

    // tooltip 가로 위치 — anchorPositionByRatio 만큼 왼쪽에서 안으로
    let left = anchorX - tipRect.width * anchorPositionByRatio;

    // viewport 좌우 8px 마진 clamp
    const vw = window.innerWidth;
    const minLeft = 8;
    const maxLeft = vw - tipRect.width - 8;
    if (maxLeft >= minLeft) left = Math.max(minLeft, Math.min(maxLeft, left));

    // 화살표 위치 (tooltip 좌측 기준 px) — anchorX 와 left 차이로 재계산
    let arrowLeft = anchorX - left;
    arrowLeft = Math.max(arrowMeta.w / 2 + 4, Math.min(tipRect.width - arrowMeta.w / 2 - 4, arrowLeft));

    // 세로 위치 + autoFlip
    let actualPlacement = placement;
    let top;
    if (placement === "bottom") {
      top = rect.bottom + effOffset;
      if (autoFlip && top + tipRect.height > window.innerHeight - 8) {
        const altTop = rect.top - tipRect.height - effOffset;
        if (altTop >= 8) {
          actualPlacement = "top";
          top = altTop;
        }
      }
    } else {
      top = rect.top - tipRect.height - effOffset;
      if (autoFlip && top < 8) {
        const altTop = rect.bottom + effOffset;
        if (altTop + tipRect.height <= window.innerHeight - 8) {
          actualPlacement = "bottom";
          top = altTop;
        }
      }
    }

    // strategy=absolute → window.scrollX/Y 더하기
    if (strategy === "absolute") {
      top += window.scrollY || window.pageYOffset || 0;
      left += window.scrollX || window.pageXOffset || 0;
    }

    setPos({
      top: Math.round(top),
      left: Math.round(left),
      arrowLeft: Math.round(arrowLeft),
      flipped: actualPlacement !== placement,
      actualPlacement: actualPlacement,
    });
  }, [size, offset, anchorPositionByRatio, placement, autoFlip, strategy]);

  // ----- 마운트 / 가시성 토글 -----
  __ttUseEffect(function () {
    if (effectiveOpen) {
      setMounted(true);
      // 다음 frame 에 위치 계산 + visible
      const id1 = requestAnimationFrame(function () {
        enterRafRef.current = requestAnimationFrame(function () {
          computePosition();
          setVisible(true);
        });
      });
      return function () {
        cancelAnimationFrame(id1);
        if (enterRafRef.current) cancelAnimationFrame(enterRafRef.current);
      };
    } else {
      setVisible(false);
    }
  }, [effectiveOpen, computePosition]);

  // 퇴장 후 unmount
  __ttUseEffect(function () {
    if (mounted && !effectiveOpen && !visible) {
      const exitMs = motionVariant === "strong" ? 200 : 120;
      exitTimerRef.current = setTimeout(function () {
        setMounted(false);
      }, exitMs);
      return function () {
        if (exitTimerRef.current) {
          clearTimeout(exitTimerRef.current);
          exitTimerRef.current = null;
        }
      };
    }
  }, [mounted, effectiveOpen, visible, motionVariant]);

  // ----- 위치 재계산 (스크롤 / resize) -----
  __ttUseEffect(function () {
    if (!visible) return;
    const onScrollOrResize = function () {
      if (repositionRafRef.current) cancelAnimationFrame(repositionRafRef.current);
      repositionRafRef.current = requestAnimationFrame(computePosition);
    };
    window.addEventListener("scroll", onScrollOrResize, true);
    window.addEventListener("resize", onScrollOrResize);
    return function () {
      window.removeEventListener("scroll", onScrollOrResize, true);
      window.removeEventListener("resize", onScrollOrResize);
      if (repositionRafRef.current) cancelAnimationFrame(repositionRafRef.current);
    };
  }, [visible, computePosition]);

  // ----- dismissible: outside click + ESC -----
  __ttUseEffect(function () {
    if (!visible) return;
    if (!dismissible) return;
    const onDocClick = function (e) {
      const t = e.target;
      if (tooltipRef.current && tooltipRef.current.contains(t)) return;
      if (triggerRef.current && triggerRef.current.contains(t)) return;
      requestSetOpen(false);
    };
    const onKey = function (e) {
      if (e.key === "Escape") {
        requestSetOpen(false);
      }
    };
    document.addEventListener("mousedown", onDocClick, true);
    document.addEventListener("touchstart", onDocClick, true);
    document.addEventListener("keydown", onKey);
    return function () {
      document.removeEventListener("mousedown", onDocClick, true);
      document.removeEventListener("touchstart", onDocClick, true);
      document.removeEventListener("keydown", onKey);
    };
  }, [visible, dismissible, requestSetOpen]);

  // ----- 트리거 prop 합성 (cloneElement) -----
  const childArr = React.Children.toArray(children).filter(Boolean);
  const child = childArr[0];
  if (!React.isValidElement(child)) {
    // children 이 valid element 가 아니면 그냥 children 만 반환
    return children == null ? null : <React.Fragment>{children}</React.Fragment>;
  }

  const childRef = child.ref;
  const setTriggerRef = function (node) {
    triggerRef.current = node;
    if (typeof childRef === "function") childRef(node);
    else if (childRef && typeof childRef === "object") childRef.current = node;
  };

  // 기존 핸들러 체이닝
  function chain(orig, ours) {
    return function (e) {
      if (typeof orig === "function") orig(e);
      ours(e);
    };
  }

  const triggerExtra = {
    ref: setTriggerRef,
    "aria-describedby": mounted ? idAuto : (child.props && child.props["aria-describedby"]) || undefined,
  };

  if (openOnHover) {
    triggerExtra.onMouseEnter = chain(child.props.onMouseEnter, function () { requestSetOpen(true); });
    triggerExtra.onMouseLeave = chain(child.props.onMouseLeave, function () {
      // dismissible 이면 외부 클릭/ESC 만으로 닫게 두기 (마우스 떠나도 유지)
      if (dismissible) return;
      requestSetOpen(false);
    });
  }
  if (openOnFocus) {
    triggerExtra.onFocus = chain(child.props.onFocus, function () { requestSetOpen(true); });
    triggerExtra.onBlur  = chain(child.props.onBlur,  function () {
      if (dismissible) return;
      requestSetOpen(false);
    });
  }

  const triggerCloned = React.cloneElement(child, triggerExtra);

  // ----- 메시지 박스 렌더 -----
  const arrowMeta = __TOOLTIP_ARROW[size] || __TOOLTIP_ARROW.medium;
  const effPlacement = (pos && pos.actualPlacement) || placement;

  const cls = [
    "tds-tooltip",
    `tds-tooltip--${size}`,
    `tds-tooltip--${effPlacement}`,
    `tds-tooltip--align-${messageAlign}`,
    `tds-tooltip--motion-${motionVariant}`,
    `tds-tooltip--clip-${clipToEnd}`,
    visible ? "is-visible" : "is-leaving",
    className,
  ].filter(Boolean).join(" ");

  const boxStyle = {
    position: strategy,
    top: pos.top,
    left: pos.left,
    ...(style || {}),
  };

  const arrowStyle = {
    left: pos.arrowLeft,
  };

  // 화살표 SVG — 위치에 따라 회전
  const aw = arrowMeta.w;
  const ah = arrowMeta.h;
  const arrowSvg = (
    <svg
      className="tds-tooltip__arrow-svg"
      width={aw}
      height={ah}
      viewBox={`0 0 ${aw} ${ah}`}
      aria-hidden="true"
      focusable="false"
    >
      {/* placement=bottom: 화살표가 위쪽으로 (tooltip 위 모서리에서 트리거 향함)
          placement=top   : 화살표가 아래쪽으로 (tooltip 아래 모서리에서 트리거 향함)
          기본 path 는 위쪽을 가리키는 삼각형 — placement=top 이면 CSS 로 뒤집음 */}
      <polygon points={`0,${ah} ${aw / 2},0 ${aw},${ah}`} fill="currentColor" />
    </svg>
  );

  const tooltipNode = (
    <div
      ref={tooltipRef}
      id={idAuto}
      role="tooltip"
      aria-label={ariaLabel}
      className={cls}
      style={boxStyle}
      // 호버 인터랙션 안정화 — tooltip 위에 호버해도 닫히지 않게.
      // openOnHover 모드에서 트리거→tooltip 이동 시 mouseleave→mouseenter 의
      // 짧은 갭에 닫히는 걸 방지하는 건 사용자가 dismissible 로 명시해야 함.
      onMouseEnter={openOnHover ? function () { requestSetOpen(true); } : undefined}
      onMouseLeave={openOnHover && !dismissible ? function () { requestSetOpen(false); } : undefined}
      {...rest}
    >
      <div className="tds-tooltip__message">{message}</div>
      <div className="tds-tooltip__arrow" style={arrowStyle}>
        {arrowSvg}
      </div>
    </div>
  );

  return (
    <React.Fragment>
      {triggerCloned}
      {mounted && typeof document !== "undefined"
        ? ReactDOM.createPortal(tooltipNode, document.body)
        : null}
    </React.Fragment>
  );
}

window.Tooltip = Tooltip;
Object.assign(window, { Tooltip });
