"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.AutoMountedDialog = AutoMountedDialog;
exports.BLOOM_DIALOG_CSS = void 0;
exports.Dialog = Dialog;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactRemoveScrollBar = require("react-remove-scroll-bar");
var _indexWeb = require("../portal/index.web.js");
var _useTheme = require("../theme/use-theme.js");
var _context = require("./context.js");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const FADE_OUT_DURATION = 150;
const stopPropagation = e => e.stopPropagation();
const ClosingContext = /*#__PURE__*/(0, _react.createContext)(false);

/**
 * Web variant of `<Dialog>`.
 *
 * A centered modal card rendered into the bloom Portal at the end of
 * `document.body`. Same prop API as native — `title`, `description`,
 * `actions`, or arbitrary `children` — so call sites are platform
 * agnostic.
 *
 * Accessibility: the panel has `role="dialog"` and the `title`/`description`
 * props (when provided) become the `aria-labelledby` / `aria-describedby`
 * targets. Pressing the backdrop dismisses. Pressing `Escape` dismisses.
 * Focus is locked inside the dialog while it's open.
 */
function Dialog({
  control,
  onClose,
  testID,
  title,
  description,
  actions,
  style,
  label,
  children
}) {
  const [isOpen, setIsOpen] = (0, _react.useState)(false);
  const [isClosing, setIsClosing] = (0, _react.useState)(false);
  const closeCallbacksRef = (0, _react.useRef)([]);
  const open = (0, _react.useCallback)(() => {
    setIsClosing(false);
    setIsOpen(true);
  }, []);
  const close = (0, _react.useCallback)(cb => {
    if (typeof cb === 'function') {
      closeCallbacksRef.current.push(cb);
    }
    setIsClosing(true);
  }, []);
  (0, _react.useEffect)(() => {
    if (!isClosing) return;
    const timer = setTimeout(() => {
      setIsOpen(false);
      setIsClosing(false);
      const queued = closeCallbacksRef.current;
      closeCallbacksRef.current = [];
      for (const cb of queued) {
        try {
          cb();
        } catch (e) {
          if (typeof console !== 'undefined' && console.error) {
            console.error('Dialog close callback error:', e);
          }
        }
      }
      onClose?.();
    }, FADE_OUT_DURATION);
    return () => clearTimeout(timer);
  }, [isClosing, onClose]);

  // Escape-to-close while open. The listener is intentionally scoped to the
  // open lifetime so stacked dialogs don't fight for the keydown — the
  // top-most one wins via document-level event order.
  (0, _react.useEffect)(() => {
    if (!isOpen || typeof document === 'undefined') return;
    const handler = e => {
      if (e.key === 'Escape') {
        e.stopPropagation();
        close();
      }
    };
    document.addEventListener('keydown', handler);
    return () => document.removeEventListener('keydown', handler);
  }, [close, isOpen]);
  (0, _react.useImperativeHandle)(control.ref, () => ({
    open,
    close
  }), [open, close]);
  const context = (0, _react.useMemo)(() => ({
    close,
    isWithinDialog: true
  }), [close]);
  if (!isOpen) return null;
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_indexWeb.Portal, {
    children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_context.Context.Provider, {
      value: context,
      children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(ClosingContext.Provider, {
        value: isClosing,
        children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactRemoveScrollBar.RemoveScrollBar, {}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
          onPress: () => close()
          // `pointerEvents: 'auto'` opts back in from the Portal root's
          // `pointer-events: none`, which is set so the idle portal
          // doesn't intercept clicks on the underlying app.
          ,
          style: {
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            zIndex: 50,
            alignItems: 'center',
            justifyContent: 'center',
            paddingHorizontal: 20,
            pointerEvents: 'auto'
          },
          children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(DialogBackdrop, {
            isClosing: isClosing
          }), /*#__PURE__*/(0, _jsxRuntime.jsx)(DialogPanel, {
            testID: testID,
            label: label,
            title: title,
            description: description,
            actions: actions,
            style: style,
            isClosing: isClosing,
            children: children
          })]
        })]
      })
    })
  });
}
function DialogPanel({
  testID,
  label,
  title,
  description,
  actions,
  style,
  isClosing,
  children
}) {
  const theme = (0, _useTheme.useTheme)();
  const titleId = (0, _react.useId)();
  const descriptionId = (0, _react.useId)();
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
    role: "dialog",
    "aria-label": label,
    "aria-labelledby": title ? titleId : undefined,
    "aria-describedby": description ? descriptionId : undefined,
    testID: testID,
    onStartShouldSetResponder: () => true,
    onResponderRelease: stopPropagation,
    onClick: stopPropagation,
    style: [{
      position: 'relative',
      borderRadius: 20,
      width: '100%',
      maxWidth: 480,
      backgroundColor: theme.colors.background,
      borderWidth: 1,
      borderColor: theme.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)',
      shadowColor: '#000',
      shadowOpacity: theme.isDark ? 0.4 : 0.1,
      shadowRadius: 30,
      shadowOffset: {
        width: 0,
        height: 4
      },
      padding: 20,
      zIndex: 60
    }, isClosing ? {
      animation: `bloomDialogZoomFadeOut ease-in ${FADE_OUT_DURATION}ms forwards`
    } : {
      animation: 'bloomDialogZoomFadeIn cubic-bezier(0.16, 1, 0.3, 1) 0.3s'
    }, style],
    children: [title ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
      nativeID: titleId,
      style: {
        fontSize: 22,
        fontWeight: '600',
        color: theme.colors.text,
        paddingBottom: description ? 4 : 16,
        lineHeight: 30
      },
      children: title
    }) : null, description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
      nativeID: descriptionId,
      style: {
        fontSize: 16,
        color: theme.colors.textSecondary,
        paddingBottom: 16,
        lineHeight: 22
      },
      children: description
    }) : null, children, actions && actions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ActionRow, {
      actions: actions
    }) : null]
  });
}
function ActionRow({
  actions
}) {
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
    style: {
      width: '100%',
      gap: 8,
      justifyContent: 'flex-end'
    },
    children: actions.map((action, idx) => /*#__PURE__*/(0, _jsxRuntime.jsx)(ActionButton, {
      action: action
    }, `${action.label}-${idx}`))
  });
}
function ActionButton({
  action
}) {
  const {
    close
  } = (0, _context.useDialogContext)();
  const theme = (0, _useTheme.useTheme)();
  const color = action.color ?? 'default';
  const shouldCloseOnPress = action.shouldCloseOnPress ?? true;
  const {
    background,
    foreground
  } = getActionPalette(color, theme.colors);
  const handlePress = (0, _react.useCallback)(e => {
    const onPress = action.onPress;
    if (color === 'cancel') {
      close(onPress ? () => onPress(e) : undefined);
      return;
    }
    if (shouldCloseOnPress) {
      close(onPress ? () => onPress(e) : undefined);
    } else {
      onPress?.(e);
    }
  }, [action.onPress, close, color, shouldCloseOnPress]);
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
    style: {
      borderRadius: 9999,
      alignItems: 'center',
      justifyContent: 'center',
      backgroundColor: background,
      opacity: action.disabled ? 0.5 : 1,
      paddingVertical: 12,
      paddingHorizontal: 24
    },
    onPress: handlePress,
    disabled: action.disabled,
    activeOpacity: 0.7,
    testID: action.testID,
    children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
      style: {
        fontSize: 16,
        fontWeight: '500',
        color: foreground
      },
      children: action.label
    })
  });
}
function getActionPalette(color, colors) {
  switch (color) {
    case 'destructive':
      return {
        background: colors.negative,
        foreground: colors.negativeForeground
      };
    case 'cancel':
      return {
        background: colors.contrast50,
        foreground: colors.text
      };
    case 'default':
      return {
        background: colors.primary,
        foreground: colors.primaryForeground
      };
    /* c8 ignore next 3 -- TS exhaustiveness guard */
    default:
      {
        const _exhaustive = color;
        return {
          background: colors.primary,
          foreground: colors.primaryForeground
        };
      }
  }
}
function DialogBackdrop({
  isClosing
}) {
  const style = [{
    position: 'fixed',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.8)'
  }, isClosing ? {
    animation: `bloomDialogFadeOut ease-in ${FADE_OUT_DURATION}ms forwards`
  } : {
    animation: 'bloomDialogFadeIn ease-out 0.15s'
  }];
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
    style: style
  });
}

/**
 * Inline imperative dialog used by `alert()`. Mounts and immediately
 * presents; resolves the host's `onResolve` once the dialog has finished
 * its exit animation.
 */
function AutoMountedDialog({
  title,
  description,
  actions,
  onResolve
}) {
  const control = (0, _context.useDialogControl)();
  (0, _react.useEffect)(() => {
    control.open();
  }, [control]);
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(Dialog, {
    control: control,
    title: title,
    description: description,
    actions: actions,
    onClose: onResolve
  });
}

/**
 * CSS keyframes required for web dialog animations.
 * Consumers should inject this string into a <style> tag or their global CSS:
 *
 * ```css
 * @keyframes bloomDialogFadeIn { from { opacity: 0; } to { opacity: 1; } }
 * @keyframes bloomDialogFadeOut { from { opacity: 1; } to { opacity: 0; } }
 * @keyframes bloomDialogZoomFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
 * @keyframes bloomDialogZoomFadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } }
 * ```
 */
const BLOOM_DIALOG_CSS = exports.BLOOM_DIALOG_CSS = `
@keyframes bloomDialogFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes bloomDialogFadeOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes bloomDialogZoomFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes bloomDialogZoomFadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } }
`;
//# sourceMappingURL=Dialog.web.js.map