// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import PropTypes from 'prop-types';
import React from 'react';
import {intlShape} from 'react-intl';
import {
    Clipboard,
    Image,
    Linking,
    Platform,
    StyleSheet,
    Text,
    View,
} from 'react-native';

import FormattedText from 'app/components/formatted_text';
import ProgressiveImage from 'app/components/progressive_image';
import TouchableWithFeedback from 'app/components/touchable_with_feedback';
import CustomPropTypes from 'app/constants/custom_prop_types';
import EphemeralStore from 'app/store/ephemeral_store';
import mattermostManaged from 'app/mattermost_managed';
import BottomSheet from 'app/utils/bottom_sheet';
import {previewImageAtIndex, calculateDimensions, isGifTooLarge} from 'app/utils/images';
import {normalizeProtocol} from 'app/utils/url';

import brokenImageIcon from 'assets/images/icons/brokenimage.png';

const ANDROID_MAX_HEIGHT = 4096;
const ANDROID_MAX_WIDTH = 4096;
const VIEWPORT_IMAGE_OFFSET = 66;
const VIEWPORT_IMAGE_REPLY_OFFSET = 13;

export default class MarkdownImage extends React.PureComponent {
    static propTypes = {
        children: PropTypes.node,
        deviceHeight: PropTypes.number.isRequired,
        deviceWidth: PropTypes.number.isRequired,
        imagesMetadata: PropTypes.object,
        linkDestination: PropTypes.string,
        isReplyPost: PropTypes.bool,
        source: PropTypes.string.isRequired,
        errorTextStyle: CustomPropTypes.Style,
    };

    static contextTypes = {
        intl: intlShape.isRequired,
    };

    constructor(props) {
        super(props);

        const dimensions = props.imagesMetadata?.[props.source];
        this.state = {
            originalHeight: dimensions?.height || 0,
            originalWidth: dimensions?.width || 0,
            failed: false,
            uri: null,
        };

        this.mounted = false;
    }

    componentDidMount() {
        this.mounted = true;

        this.setImageUrl(this.getSource());
    }

    static getDerivedStateFromProps(props) {
        const imageMetadata = props.imagesMetadata?.[props.source];

        if (imageMetadata) {
            return {
                originalHeight: imageMetadata.height,
                originalWidth: imageMetadata.width,
            };
        }

        return null;
    }

    componentDidUpdate(prevProps) {
        if (this.props.source !== prevProps.source) {
            // getSource also depends on serverURL, but that shouldn't change while this is mounted
            this.setImageUrl(this.getSource());
        }
    }

    componentWillUnmount() {
        this.mounted = false;
    }

    setImageRef = (ref) => {
        this.imageRef = ref;
    }

    setItemRef = (ref) => {
        this.itemRef = ref;
    }

    getSource = () => {
        let source = this.props.source;

        if (source.startsWith('/')) {
            source = EphemeralStore.currentServerUrl + source;
        }

        return source;
    };

    getViewPortWidth = () => {
        const {deviceHeight, deviceWidth, isReplyPost} = this.props;
        const deviceSize = deviceWidth > deviceHeight ? deviceHeight : deviceWidth;
        return deviceSize - VIEWPORT_IMAGE_OFFSET - (isReplyPost ? VIEWPORT_IMAGE_REPLY_OFFSET : 0);
    };

    handleSizeReceived = (width, height) => {
        if (!this.mounted) {
            return;
        }

        if (!width || !height) {
            this.setState({failed: true});
            return;
        }

        this.setState({
            failed: false,
            originalHeight: height,
            originalWidth: width,
        });
    };

    handleSizeFailed = () => {
        if (!this.mounted) {
            return;
        }

        this.setState({
            failed: true,
        });
    };

    handleLinkPress = () => {
        const url = normalizeProtocol(this.props.linkDestination);

        Linking.canOpenURL(url).then((supported) => {
            if (supported) {
                Linking.openURL(url);
            }
        });
    };

    handleLinkLongPress = async () => {
        const {formatMessage} = this.context.intl;

        const config = mattermostManaged.getCachedConfig();

        if (config?.copyAndPasteProtection !== 'true') {
            const cancelText = formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'});
            const actionText = formatMessage({id: 'mobile.markdown.link.copy_url', defaultMessage: 'Copy URL'});
            BottomSheet.showBottomSheetWithOptions({
                options: [actionText, cancelText],
                cancelButtonIndex: 1,
            }, (value) => {
                if (value !== 1) {
                    this.handleLinkCopy();
                }
            });
        }
    };

    handleLinkCopy = () => {
        Clipboard.setString(this.props.linkDestination || this.props.source);
    };

    handlePreviewImage = () => {
        const {
            originalHeight,
            originalWidth,
            uri,
        } = this.state;
        const link = this.getSource();
        let filename = link.substring(link.lastIndexOf('/') + 1, link.indexOf('?') === -1 ? link.length : link.indexOf('?'));
        const extension = filename.split('.').pop();

        if (extension === filename) {
            const ext = filename.indexOf('.') === -1 ? '.png' : filename.substring(filename.lastIndexOf('.'));
            filename = `${filename}${ext}`;
        }

        const files = [{
            caption: filename,
            dimensions: {
                width: originalWidth,
                height: originalHeight,
            },
            source: {uri},
            data: {
                localPath: uri,
            },
        }];

        previewImageAtIndex([this.itemRef], 0, files);
    };

    loadImageSize = (source) => {
        if (!this.state.originalWidth) {
            Image.getSize(source, this.handleSizeReceived, this.handleSizeFailed);
        }
    };

    setImageUrl = (imageURL) => {
        const uri = imageURL;

        this.setState({uri});
        this.loadImageSize(uri);
    };

    render() {
        if (isGifTooLarge(this.props.imagesMetadata?.[this.props.source])) {
            return null;
        }

        let image = null;
        const {originalHeight, originalWidth, uri} = this.state;
        const {height, width} = calculateDimensions(originalHeight, originalWidth, this.getViewPortWidth());

        if (width && height) {
            if (Platform.OS === 'android' && (width > ANDROID_MAX_WIDTH || height > ANDROID_MAX_HEIGHT)) {
                // Android has a cap on the max image size that can be displayed

                image = (
                    <Text style={this.props.errorTextStyle}>
                        <FormattedText
                            id='mobile.markdown.image.too_large'
                            defaultMessage='Image exceeds max dimensions of {maxWidth} by {maxHeight}:'
                            values={{
                                maxWidth: ANDROID_MAX_WIDTH,
                                maxHeight: ANDROID_MAX_HEIGHT,
                            }}
                        />
                        {' '}
                        {this.props.children}
                    </Text>
                );
            } else {
                // React Native complains if we try to pass resizeMode as a style
                let source = null;
                if (uri) {
                    source = {uri};
                }

                image = (
                    <TouchableWithFeedback
                        onLongPress={this.handleLinkLongPress}
                        onPress={this.handlePreviewImage}
                        style={{width, height}}
                    >
                        <ProgressiveImage
                            ref={this.setImageRef}
                            defaultSource={source}
                            resizeMode='contain'
                            style={{width, height}}
                        />
                    </TouchableWithFeedback>
                );
            }
        } else if (this.state.failed) {
            image = (
                <Image
                    source={brokenImageIcon}
                    style={style.brokenImageIcon}
                />
            );
        }

        if (image && this.props.linkDestination) {
            image = (
                <TouchableWithFeedback
                    onPress={this.handleLinkPress}
                    onLongPress={this.handleLinkLongPress}
                >
                    {image}
                </TouchableWithFeedback>
            );
        }

        return (
            <View
                ref={this.setItemRef}
                style={style.container}
            >
                {image}
            </View>
        );
    }
}

const style = StyleSheet.create({
    container: {
        marginBottom: 5,
    },
    brokenImageIcon: {
        width: 24,
        height: 24,
    },
});
