"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _roomsSlice = require("../../roomStore/roomsSlice");
var _Composing = _interopRequireDefault(require("../styled/StyledInputComponents/Composing"));
var _TreadLabel = _interopRequireDefault(require("../styled/TreadLabel"));
var _MessageContainer = require("./MessageContainer");
var _useRoomState = require("../../hooks/useRoomState");
var _Loader = _interopRequireDefault(require("../styled/Loader"));
var _icons = require("../../assets/icons");
var _CustomTypingIndicator = _interopRequireDefault(require("../styled/StyledInputComponents/CustomTypingIndicator"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
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); }
/** @format */

const MessageList = ({
  CustomMessage,
  CustomDaySeparator,
  CustomNewMessageLabel,
  user,
  loadMoreMessages,
  roomJID,
  config,
  loading,
  isReply,
  activeMessage
}) => {
  const {
    composing,
    messages,
    composingList
  } = (0, _useRoomState.useRoomState)(roomJID).room;
  const [isUserAtBottom, setIsUserAtBottom] = (0, _react.useState)(true);
  const [showNewMessageIndicator, setShowNewMessageIndicator] = (0, _react.useState)(false);
  const [isLoadingMore, setIsLoadingMore] = (0, _react.useState)(false);
  const [isScrollBlocked, setIsScrollBlocked] = (0, _react.useState)(false);
  // Count of messages that arrived while the user was scrolled up.
  // Renders as a badge on the scroll-to-bottom arrow so they know how
  // much they're missing without having to scroll to find out.
  const [unreadWhileScrolledUp, setUnreadWhileScrolledUp] = (0, _react.useState)(0);
  // Newest message timestamp at the moment the user scrolled away from
  // the bottom. The badge counts only messages NEWER than this, so
  // back-pagination (loading OLDER history while scrolled up) never
  // inflates the count. Was previously a message-COUNT snapshot, which
  // wrongly counted back-paginated old messages as "new" (Android repro).
  const newestSeenTsRef = (0, _react.useRef)(null);
  // Refs mirror the viewport state synchronously. The auto-follow logic
  // runs from FlatList callbacks where setState hasn't necessarily
  // committed yet; relying on stale React state is what caused the chat
  // to jump back to the bottom when a new message landed mid-scroll.
  const isUserAtBottomRef = (0, _react.useRef)(true);
  const hasUserScrolledRef = (0, _react.useRef)(false);
  const flatListRef = (0, _react.useRef)(null);
  const addReplyMessages = (0, _react.useMemo)(() => {
    return messages.map(message => {
      const newMessage = {
        ...message,
        reply: messages.filter(mess => !!mess.mainMessage && JSON.parse(mess.mainMessage).id === message.id && !mess.isDeleted)
      };
      return newMessage;
    });
  }, [messages]);
  const memoizedMessages = (0, _react.useMemo)(() => {
    const nonDeletedMessages = addReplyMessages.filter(item => !item.isDeleted);
    let filtered;
    if (isReply) {
      filtered = nonDeletedMessages.filter(item => item.roomJid === roomJID && item.isReply && item.isReply === 'true' && item.mainMessage && JSON.parse(item.mainMessage).id === activeMessage?.id);
    } else {
      filtered = nonDeletedMessages.filter(item => item.showInChannel === 'true' || (!item.isReply || item.isReply === 'false') && !item.mainMessage);
    }

    // Explicit chronological sort by stanza-id timestamp (microseconds-
    // since-epoch encoded in the id). Without this, MAM history that
    // streams in across multiple ticks visibly reorders itself:
    // insertMessageWithDelimiter inserts new arrivals at the slot
    // matching their date.toString() comparison, but redux state can
    // emit intermediate snapshots between dispatches, so the FlatList
    // briefly renders messages out-of-order until the final batch
    // settles. Sorting here makes each render a stable, ordered view —
    // even mid-stream — so the user sees them appear from oldest to
    // newest with no swap animation.
    const sorted = filtered.slice().sort((a, b) => {
      const aNum = new Date(a?.date).getTime() || 0;
      const bNum = new Date(b?.date).getTime() || 0;
      return aNum - bNum;
    });
    const deduped = Array.from(new Map(sorted.map(m => [m.id, m])).values());
    return deduped;
  }, [addReplyMessages, isReply, roomJID, activeMessage]);
  const handleLoadMore = (0, _react.useCallback)(async () => {
    if (loading || isLoadingMore || !memoizedMessages.length || isScrollBlocked) {
      return;
    }
    const oldestMessage = memoizedMessages[0];
    if (!oldestMessage || !oldestMessage.id) {
      return;
    }
    setIsLoadingMore(true);
    setIsScrollBlocked(true);
    try {
      await loadMoreMessages(oldestMessage.roomJid || roomJID, 15, Number(oldestMessage.id));
    } catch (error) {
      console.error(error);
    } finally {
      setTimeout(() => {
        setIsLoadingMore(false);
        setIsScrollBlocked(false);
      }, 500);
    }
  }, [memoizedMessages, loadMoreMessages, loading, isLoadingMore, isScrollBlocked, roomJID]);
  const dataMessages = (0, _react.useMemo)(() => {
    const base = memoizedMessages.slice().reverse();
    const boundaryTs = newestSeenTsRef.current;
    if (isUserAtBottomRef.current || boundaryTs === null || unreadWhileScrolledUp === 0) {
      return base;
    }
    let lastNewIndex = -1;
    for (let i = 0; i < base.length; i++) {
      if ((0, _roomsSlice.msgSortableMs)(base[i]) > boundaryTs) {
        lastNewIndex = i;
      } else {
        break;
      }
    }
    if (lastNewIndex === -1) {
      return base;
    }
    const withDivider = base.slice();
    withDivider.splice(lastNewIndex + 1, 0, {
      id: 'delimiter-new-local',
      user: {
        id: 'system',
        name: undefined,
        token: '',
        refreshToken: ''
      },
      date: new Date(boundaryTs).toISOString(),
      body: 'New Messages',
      roomJid: roomJID
    });
    return withDivider;
  }, [memoizedMessages, isUserAtBottom, roomJID, unreadWhileScrolledUp]);
  const renderMessage = (0, _react.useCallback)(({
    item,
    index
  }) => {
    if (String(item.id).startsWith('delimiter-new')) {
      return /*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageContainer.MessageContainer, {
        CustomMessage: CustomMessage,
        CustomDaySeparator: CustomDaySeparator,
        CustomNewMessageLabel: CustomNewMessageLabel,
        message: item,
        activeMessage: activeMessage,
        config: config,
        walletAddress: user.xmppUsername || user.walletAddress,
        isReply: isReply,
        showDateLabel: false
      });
    }
    const messageDate = new Date(item.date).toDateString();
    let showDateLabel = false;
    let nextMessage = null;
    for (let i = index + 1; i < dataMessages.length; i++) {
      const candidate = dataMessages[i];
      if (!String(candidate.id).startsWith('delimiter-new')) {
        nextMessage = candidate;
        break;
      }
    }
    const nextMessageDate = nextMessage ? new Date(nextMessage.date).toDateString() : null;
    if (!nextMessage || messageDate !== nextMessageDate) {
      showDateLabel = true;
    }
    return /*#__PURE__*/(0, _jsxRuntime.jsx)(_MessageContainer.MessageContainer, {
      CustomMessage: CustomMessage,
      CustomDaySeparator: CustomDaySeparator,
      CustomNewMessageLabel: CustomNewMessageLabel,
      message: item,
      activeMessage: activeMessage,
      config: config,
      walletAddress: user.xmppUsername || user.walletAddress,
      isReply: isReply,
      showDateLabel: showDateLabel
    });
  }, [activeMessage, config, CustomDaySeparator, CustomMessage, CustomNewMessageLabel, dataMessages, isReply, user.xmppUsername, user.walletAddress]);
  const scrollToBottom = (0, _react.useCallback)(() => {
    if (flatListRef.current) {
      flatListRef.current.scrollToOffset({
        offset: 0,
        animated: true
      });
    }
  }, [flatListRef]);
  const handleContentSizeChange = (0, _react.useCallback)(() => {
    if (!isLoadingMore && flatListRef.current && isUserAtBottomRef.current) {
      scrollToBottom();
    } else if (!isLoadingMore && hasUserScrolledRef.current) {
      setShowNewMessageIndicator(true);
    }
  }, [scrollToBottom, isLoadingMore]);
  const handleScroll = (0, _react.useCallback)(event => {
    const contentOffset = event.nativeEvent.contentOffset.y;

    // Don't force-dismiss the keyboard on every scroll event — any
    // touch on the message list used to close it, which made replying
    // to a long thread feel broken. Drag-to-dismiss still works via
    // FlatList's keyboardDismissMode="interactive" below.

    if (contentOffset > 150 && !hasUserScrolledRef.current) {
      hasUserScrolledRef.current = true;
    }
    if (contentOffset < 150) {
      isUserAtBottomRef.current = true;
      setShowNewMessageIndicator(false);
      setIsUserAtBottom(true);
      // Back at the bottom — clear the unread-while-scrolled-up
      // counter so the badge disappears with the arrow.
      setUnreadWhileScrolledUp(0);
      newestSeenTsRef.current = null;
    } else {
      isUserAtBottomRef.current = false;
      if (hasUserScrolledRef.current) {
        setShowNewMessageIndicator(true);
      }
      if (newestSeenTsRef.current === null) {
        // Snapshot the NEWEST message's timestamp the moment the user
        // first leaves the bottom. Anything newer than this that
        // arrives later is genuinely "new"; older history loaded by
        // back-pagination is < this and never counts.
        newestSeenTsRef.current = memoizedMessages.reduce((mx, m) => {
          const t = (0, _roomsSlice.msgSortableMs)(m);
          return t > mx ? t : mx;
        }, 0);
      }
      setIsUserAtBottom(false);
    }
  }, [memoizedMessages]);

  // Keep the badge in sync with messages that arrive while the user is
  // scrolled up. Count ONLY messages newer than the newest-seen snapshot
  // — so loading older history (back-pagination) doesn't inflate it.
  (0, _react.useEffect)(() => {
    if (isUserAtBottomRef.current) {
      return;
    }
    if (newestSeenTsRef.current === null) {
      return;
    }
    const since = newestSeenTsRef.current;
    const count = memoizedMessages.reduce((n, m) => (0, _roomsSlice.msgSortableMs)(m) > since ? n + 1 : n, 0);
    setUnreadWhileScrolledUp(count);
  }, [memoizedMessages, isUserAtBottom]);
  const handleLayout = () => {
    isUserAtBottomRef.current = true;
    hasUserScrolledRef.current = false;
    setIsUserAtBottom(true);
  };
  const handleNewMessageIndicatorPress = () => {
    isUserAtBottomRef.current = true;
    setShowNewMessageIndicator(false);
    setIsUserAtBottom(true);
    setUnreadWhileScrolledUp(0);
    newestSeenTsRef.current = null;
    scrollToBottom();
  };
  (0, _react.useEffect)(() => {
    hasUserScrolledRef.current = false;
    isUserAtBottomRef.current = true;
    setShowNewMessageIndicator(false);
    setIsUserAtBottom(true);
    setUnreadWhileScrolledUp(0);
    newestSeenTsRef.current = null;
  }, [roomJID]);
  const BackgroundImage = (0, _react.useMemo)(() => {
    const image = config?.backgroundChat?.image;
    if (image) {
      if (typeof image === 'function') {
        const SvgComponent = image;
        return /*#__PURE__*/(0, _jsxRuntime.jsx)(SvgComponent, {
          width: "100%"
        });
      } else {
        return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
          source: image
        });
      }
    }
    return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {});
  }, [config?.backgroundChat?.image]);
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
    style: [styles.container, {
      backgroundColor: config?.backgroundChat?.color || '#F3F6FC'
    }],
    children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
      style: styles.backgroundImageContainer,
      children: BackgroundImage
    }), activeMessage && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
      children: [CustomMessage && /*#__PURE__*/(0, _jsxRuntime.jsx)(CustomMessage, {
        message: activeMessage,
        isUser: activeMessage.user.id === user.walletAddress,
        isReply: isReply
      }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_TreadLabel.default, {
        reply: memoizedMessages.length,
        colors: config?.colors
      })]
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
      ref: flatListRef,
      data: dataMessages,
      renderItem: renderMessage,
      keyExtractor: item => item.id.toString(),
      onEndReached: handleLoadMore,
      onScroll: handleScroll,
      onContentSizeChange: handleContentSizeChange,
      onEndReachedThreshold: 0.1,
      scrollEventThrottle: 16,
      onLayout: handleLayout,
      inverted: true,
      keyboardShouldPersistTaps: "handled",
      keyboardDismissMode: "interactive",
      contentContainerStyle: styles.flatListContent,
      ListFooterComponent: loading && memoizedMessages.length > 15 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_Loader.default, {
        color: config?.colors?.primary
      }) : null
    }), showNewMessageIndicator && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
      style: [styles.newMessageIndicator, {
        backgroundColor: config?.colors?.primary || '#0052CD'
      }],
      onPress: handleNewMessageIndicatorPress,
      children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.ArowDownIcon, {
        color: "#FFFFFF",
        width: 22,
        height: 22
      }), unreadWhileScrolledUp > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
        style: styles.newMessageBadge,
        children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
          style: styles.newMessageBadgeText,
          children: unreadWhileScrolledUp > 99 ? '99+' : unreadWhileScrolledUp
        })
      })]
    }), config?.customTypingIndicator?.enabled && composing && /*#__PURE__*/(0, _jsxRuntime.jsx)(_CustomTypingIndicator.default, {
      usersTyping: composingList || ['User'],
      text: config.customTypingIndicator.text,
      position: config.customTypingIndicator.position || 'bottom',
      styles: config.customTypingIndicator.styles,
      customComponent: config.customTypingIndicator.customComponent,
      isVisible: composing
    }), !config?.customTypingIndicator?.enabled && config?.disableHeader && composing && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Composing.default, {
      usersTyping: composingList || ['User']
    })]
  });
};
var _default = exports.default = MessageList;
const styles = _reactNative.StyleSheet.create({
  container: {
    flex: 1,
    width: '100%'
  },
  flatListContent: {
    paddingHorizontal: 10,
    paddingBottom: 10
  },
  messageList: {
    paddingHorizontal: 10,
    flexGrow: 1,
    backgroundColor: '#434343'
  },
  backgroundImageContainer: {
    width: '100%',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: -1
  },
  image: {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%'
  },
  newMessageIndicator: {
    position: 'absolute',
    width: 40,
    height: 40,
    bottom: 20,
    right: 20,
    backgroundColor: '#0052CD',
    borderRadius: 20,
    justifyContent: 'center',
    alignItems: 'center',
    // Lift the FAB off the chat background so it reads as a button.
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2
    },
    shadowOpacity: 0.2,
    shadowRadius: 3,
    elevation: 4
  },
  newMessageText: {
    color: '#fff',
    fontWeight: 'bold'
  },
  newMessageBadge: {
    position: 'absolute',
    top: -6,
    right: -6,
    minWidth: 20,
    height: 20,
    paddingHorizontal: 6,
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    // Fixed red so it stays distinct from the (primary-coloured) FAB,
    // with a white ring to separate the two.
    backgroundColor: '#E53935',
    borderWidth: 2,
    borderColor: '#fff'
  },
  newMessageBadgeText: {
    color: '#fff',
    fontWeight: 'bold',
    fontSize: 11
  }
});
//# sourceMappingURL=MessageList.js.map