"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _StyledInputComponents = require("./StyledInputComponents/StyledInputComponents");
var _Button = _interopRequireDefault(require("./Button"));
var _icons = require("../../assets/icons");
var _reactNative = require("react-native");
var _AttachSheet = _interopRequireDefault(require("../Modals/AttachSheet/AttachSheet"));
var _MediaFilePreview = require("./MediaFilePreview");
var ImagePicker = _interopRequireWildcard(require("expo-image-picker"));
var DocumentPicker = _interopRequireWildcard(require("expo-document-picker"));
var ImageManipulator = _interopRequireWildcard(require("expo-image-manipulator"));
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 */

// iOS photos default to HEIC; many web backends (incl. ours) 500 on
// HEIC uploads because they can't decode it. Convert to JPEG before
// upload — JPEG is universally supported. JPEGs pass through.
const normalizeImageAsset = async asset => {
  const mime = (asset.mimeType || '').toLowerCase();
  const looksLikeHeic = mime.includes('heic') || mime.includes('heif') || asset.uri.toLowerCase().endsWith('.heic') || asset.uri.toLowerCase().endsWith('.heif');
  if (!looksLikeHeic) {
    return {
      uri: asset.uri,
      mime: asset.mimeType || 'image/jpeg',
      name: asset.fileName || asset.uri.split('/').pop() || `image_${Date.now()}.jpg`
    };
  }
  const result = await ImageManipulator.manipulateAsync(asset.uri, [], {
    compress: 0.9,
    format: ImageManipulator.SaveFormat.JPEG
  });
  const baseName = (asset.fileName || asset.uri.split('/').pop() || `image_${Date.now()}`).replace(/\.(heic|heif)$/i, '.jpg');
  return {
    uri: result.uri,
    mime: 'image/jpeg',
    name: baseName
  };
};
const SendInput = ({
  sendMessage,
  sendMedia,
  config,
  onFocus,
  onBlur,
  editMessage,
  isLoading,
  isMessageProcessing
}) => {
  const [message, setMessage] = (0, _react.useState)('');
  const [isRecording, setIsRecording] = (0, _react.useState)(false);
  const [isFocused, setIsFocused] = (0, _react.useState)(false);
  const [filePreviews, setFilePreviews] = (0, _react.useState)([]);
  const [inputHeight, setInputHeight] = (0, _react.useState)(40);
  const [showMediaMenu, setShowMediaMenu] = (0, _react.useState)(false);

  // Refs shadow `message` and `filePreviews` so handleSendClick can:
  //   (a) read the LATEST typed value even when React state hasn't
  //       flushed yet (rapid tap-burst race),
  //   (b) clear the ref SYNCHRONOUSLY before any await — so a follow-up
  //       tap on send that happens before the next render reads `''`
  //       and bails out (instead of re-sending the same content).
  // Without these, fast spam-typing+sending used to collapse 10 user
  // sends into 1 server message with all 10 contents stacked.
  const messageRef = (0, _react.useRef)(message);
  const filePreviewsRef = (0, _react.useRef)(filePreviews);
  // Guard flag so a 2nd tap during the first send's await is a no-op
  // (versus duplicating the send with stale closure state).
  const sendingRef = (0, _react.useRef)(false);
  const handleFileSelect = files => {
    filePreviewsRef.current = files;
    setFilePreviews([...files]);
  };

  // Both pickers use expo-image-picker / expo-document-picker. They
  // own permission prompts internally and surface a `canceled` flag in
  // the result — no need for a separate permissions library or hand-
  // rolled check/request flow.

  const promptOpenSettings = message => {
    _reactNative.Alert.alert('Permission required', message, [{
      text: 'Cancel',
      style: 'cancel'
    }, {
      text: 'Open Settings',
      onPress: () => _reactNative.Linking.openSettings()
    }]);
  };
  const handleCameraSelection = async () => {
    const perm = await ImagePicker.requestCameraPermissionsAsync();
    if (!perm.granted) {
      promptOpenSettings('Camera permission is needed to take photos and videos.');
      return;
    }
    try {
      const result = await ImagePicker.launchCameraAsync({
        mediaTypes: ['images', 'videos'],
        quality: 0.9
      });
      if (result.canceled || !result.assets?.[0]) {
        return;
      }
      const asset = result.assets[0];
      if (asset.type === 'video') {
        handleFileSelect([{
          uri: asset.uri,
          type: asset.mimeType || 'video/mp4',
          name: asset.fileName || asset.uri.split('/').pop() || `camera_${Date.now()}.mp4`
        }]);
        return;
      }
      const normalized = await normalizeImageAsset(asset);
      handleFileSelect([{
        uri: normalized.uri,
        type: normalized.mime,
        name: normalized.name
      }]);
    } catch (error) {
      console.error('Camera error:', error);
    }
  };
  const handleGallerySelection = async () => {
    const perm = await ImagePicker.requestMediaLibraryPermissionsAsync();
    if (!perm.granted) {
      promptOpenSettings('Gallery permission is needed to select photos and videos.');
      return;
    }
    try {
      const result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ['images', 'videos'],
        allowsEditing: false,
        quality: 0.9
      });
      if (result.canceled || !result.assets?.[0]) {
        return;
      }
      const asset = result.assets[0];
      // Videos pass through unchanged; only images get the HEIC→JPEG
      // normalization (manipulator is image-only).
      if (asset.type === 'video') {
        handleFileSelect([{
          uri: asset.uri,
          type: asset.mimeType || 'video/mp4',
          name: asset.fileName || asset.uri.split('/').pop() || `gallery_${Date.now()}.mp4`
        }]);
        return;
      }
      const normalized = await normalizeImageAsset(asset);
      handleFileSelect([{
        uri: normalized.uri,
        type: normalized.mime,
        name: normalized.name
      }]);
    } catch (error) {
      console.error('Gallery error:', error);
    }
  };
  const handleFileSelection = async () => {
    try {
      const result = await DocumentPicker.getDocumentAsync({
        type: '*/*',
        multiple: false,
        copyToCacheDirectory: true
      });
      if (result.canceled || !result.assets?.[0]) {
        return;
      }
      const asset = result.assets[0];
      handleFileSelect([{
        uri: asset.uri,
        type: asset.mimeType || 'application/octet-stream',
        name: asset.name || `file_${Date.now()}`
      }]);
    } catch (err) {
      console.error('DocumentPicker error:', err);
    }
  };
  const handleAttachPress = () => {
    if (_reactNative.Platform.OS === 'ios') {
      _reactNative.ActionSheetIOS.showActionSheetWithOptions({
        options: ['Cancel', 'Camera', 'Photo Library', 'Document'],
        cancelButtonIndex: 0
      }, buttonIndex => {
        if (buttonIndex === 1) {
          handleCameraSelection();
        } else if (buttonIndex === 2) {
          handleGallerySelection();
        } else if (buttonIndex === 3) {
          handleFileSelection();
        }
      });
    } else {
      setShowMediaMenu(true);
    }
  };
  const handleRemoveImage = index => {
    const next = filePreviewsRef.current.filter((_, i) => i !== index);
    filePreviewsRef.current = next;
    setFilePreviews(next);
  };
  const handleSendClick = (0, _react.useCallback)(async () => {
    // Snapshot LATEST values from refs, not from the React-state
    // closure. The closure is captured at render-time and may be
    // stale by the time a fast-fingered user spams the send button.
    const messageToSend = messageRef.current;
    const filesToSend = filePreviewsRef.current;

    // Nothing to send → bail. Critical for the spam case: after the
    // first tap clears refs, subsequent taps queued by RN's native
    // gesture system see empty refs and exit without re-sending.
    if (!messageToSend && filesToSend.length === 0) {
      return;
    }

    // Re-entrancy guard. If a previous send is still awaiting upload
    // / xmpp.send, a second tap should NOT slip through with the
    // same snapshotted content.
    if (sendingRef.current) {
      return;
    }
    sendingRef.current = true;

    // CLEAR EVERYTHING SYNCHRONOUSLY — refs first (so the early-bail
    // above catches the next rapid tap immediately), then dispatch
    // state updates so the UI catches up on the next render.
    messageRef.current = '';
    filePreviewsRef.current = [];
    setMessage('');
    setFilePreviews([]);
    try {
      for (const file of filesToSend) {
        try {
          await sendMedia(file, file.type);
        } catch (err) {
          console.error(err);
          return;
        }
      }
      if (messageToSend) {
        sendMessage(messageToSend);
      }
    } finally {
      sendingRef.current = false;
    }
    // Deps intentionally exclude `message` / `filePreviews` — we read
    // from refs, so closure freshness is irrelevant and re-creating
    // the handler on every keystroke would just thrash GC + risk the
    // (still-async) `disabled` button race.
  }, [sendMessage, sendMedia]);
  (0, _react.useEffect)(() => {
    const next = editMessage || '';
    messageRef.current = next;
    setMessage(next);
  }, [editMessage]);
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_StyledInputComponents.InputContainer, {
    isText: !!message,
    children: [filePreviews.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_MediaFilePreview.MediaFilePreview, {
      filePreviews: filePreviews,
      handleRemoveImage: handleRemoveImage
    }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_StyledInputComponents.MessageInputContainer, {
      children: [!isRecording && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
        children: [!config?.disableMedia && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
          onPress: handleAttachPress,
          style: {
            width: 40,
            height: 40,
            justifyContent: 'center',
            alignItems: 'center',
            backgroundColor: 'transparent'
          },
          activeOpacity: 0.7,
          children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.AttachIcon, {
            color: config?.colors?.primary || '#0052CD'
          })
        }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_StyledInputComponents.MessageInput
        // Stable testIDs so Maestro / Detox / Appium can drive
        // the chat-send flow reliably. accessibilityLabel
        // mirrors the testID so iOS accessibility-id lookups
        // also work. (TextInput in src/components/InputComponents
        // also carries the same testIDs as a fallback, but
        // SendInput is the input that actually ships in
        // <ChatRoom> / <Thread> so this is the one e2e drivers
        // hit in practice.)
        , {
          testID: "chat-message-input",
          accessibilityLabel: "chat-message-input",
          isFocused: isFocused,
          color: config?.colors?.primary,
          placeholder: "Type message",
          placeholderTextColor: "#999",
          value: message,
          onChangeText: text => {
            // Mirror into the ref synchronously so handleSendClick
            // reads the latest value even before the React state
            // commit lands (the source of the spam-merge bug).
            messageRef.current = text;
            setMessage(text);
          },
          onFocus: () => {
            onFocus?.();
            setIsFocused(true);
          },
          onBlur: () => {
            onBlur?.();
            setIsFocused(false);
          },
          editable: !isLoading || !isMessageProcessing,
          multiline: true
          // maxHeight={72}
          ,
          onContentSizeChange: event => {
            setInputHeight(Math.min(72, Math.max(40, event.nativeEvent.contentSize.height)));
          },
          style: {
            height: inputHeight,
            flex: 1
          }
        })]
      }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Button.default, {
        testID: "chat-send-button",
        accessibilityLabel: "chat-send-button",
        onPress: handleSendClick,
        disabled: !message && filePreviews.length === 0,
        EndIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_icons.SendIcon, {
          color: message || filePreviews.length > 0 ? '#FFFFFF' : '#D4D4D8'
        }),
        style: {
          width: 40,
          height: 40,
          borderRadius: 20,
          alignItems: 'center',
          justifyContent: 'center',
          backgroundColor: message || filePreviews.length > 0 ? config?.colors?.primary : 'transparent',
          opacity: message || filePreviews.length > 0 ? 1 : 0.5
        }
      })]
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_AttachSheet.default, {
      visible: showMediaMenu,
      onClose: () => setShowMediaMenu(false),
      onCamera: handleCameraSelection,
      onGallery: handleGallerySelection,
      onDocument: handleFileSelection,
      primaryColor: config?.colors?.primary
    })]
  });
};
var _default = exports.default = SendInput;
//# sourceMappingURL=SendInput.js.map