<template>
    <div v-loading="chatIniting" class="message-list h-100">
        <el-scrollbar
            ref="message-scrollbar"
            class="message-list-scrollbar no-bottom-scrollbar h-100"
        >
            <template v-for="item in messages">
                <div :key="item.id" class="message-template">
                    <div
                        v-if="
                            item.id > 0 &&
                            messageTimestampDictionary[item.id] &&
                            item.msg
                        "
                        class="text-center text-hint timestamp"
                    >
                        {{ format2Time(item.ts) }}
                    </div>
                    <message
                        :is-sending-message="item.id < 0"
                        :failed="item.status === -1"
                        :key="item.id"
                        :data="item"
                        :shape="shape"
                        @open="open"
                        @withdraw="refresh"
                    />
                </div>
            </template>
        </el-scrollbar>

        <image-preview v-model="preview" :file="imagePreview"></image-preview>
        <video-preview
            v-model="previewVideo"
            :file="videoPreview"
        ></video-preview>
    </div>
</template>

<script lang="ts">
    import { Component, Prop, Ref, Vue, Watch } from "vue-property-decorator";
    import { Message, MessageType } from "../model";
    import { throttle } from "../utils";
    import { formatTime } from "../utils/time";
    import ImagePreview from "./image-preview.vue";
    import message from "./message.vue";
    import VideoPreview from "./video-preview.vue";
    import { ChatStore, chatStore } from "@/customer-service/store/model";
    import { dbController } from "../database";
    import { getLastMessageId } from "../store";

    @Component({ components: { message, ImagePreview, VideoPreview } })
    export default class MessageList extends Vue {
        @chatStore.Getter(ChatStore.STATE_CHAT_MSG_HISTORY)
        private readonly historyMessage!: ChatStore.STATE_CHAT_MSG_HISTORY;

        @chatStore.Getter(ChatStore.STATE_CHAT_SENDING_MESSAGES)
        private readonly sendingMessages!: ChatStore.STATE_CHAT_SENDING_MESSAGES;

        @chatStore.State(ChatStore.STATE_CHAT_CURRENT_CHAT_ID)
        private readonly chatId!: ChatStore.STATE_CHAT_CURRENT_CHAT_ID;

        @chatStore.State(ChatStore.STATE_CURRENT_CHAT_INITING)
        private readonly chatIniting!: ChatStore.STATE_CURRENT_CHAT_INITING;

        @chatStore.Action(ChatStore.ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID)
        private readonly getLastPageMsg!: ChatStore.ACTION_GET_CHAT_MESSAGES_BEFORE_SPECIFIC_ID;

        @chatStore.Action(ChatStore.ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID)
        private readonly getNextPageMsg!: ChatStore.ACTION_GET_CHAT_MESSAGES_AFTER_SPECIFIC_ID;

        @chatStore.Mutation(ChatStore.MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM)
        private readonly saveScrollToBottomFunc!: ChatStore.MUTATION_SAVE_FUNC_SCROLL_TO_BOTTOM;

        @chatStore.Mutation(ChatStore.MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM)
        private readonly clearScrollToBottomFunc!: ChatStore.MUTATION_CLEAR_FUNC_SCROLL_TO_BOTTOM;

        @chatStore.Mutation(ChatStore.MUTATION_SAVE_FUNC_ON_NEW_MSG)
        private readonly onNewMessage!: ChatStore.MUTATION_SAVE_FUNC_ON_NEW_MSG;

        @chatStore.Mutation(ChatStore.MUTATION_CLEAR_FUNC_ON_NEW_MSG)
        private readonly clearNewMessage!: ChatStore.MUTATION_CLEAR_FUNC_ON_NEW_MSG;

        @chatStore.Mutation(ChatStore.MUTATION_WITHDRAW)
        private readonly executeWithDraw!: ChatStore.MUTATION_WITHDRAW;

        @chatStore.Action(ChatStore.ACTION_SET_CHAT_ERROR)
        private readonly setError!: ChatStore.ACTION_SET_CHAT_ERROR;

        @Prop({ default: "circle" })
        private shape!: string;

        private get messages() {
            if (this.historyMessage) {
                if (this.sendingMessages) {
                    return [...this.historyMessage, ...this.sendingMessages].filter(
                        (i) => i.chat_id === this.chatId && i.id > 0
                    );
                }
                return this.historyMessage;
            }

            if (this.sendingMessages) {
                return this.sendingMessages.filter(
                    (i) => i.chat_id === this.chatId && i.id > 0
                );
            }

            return [];
        }

        // 添加时间戳的最大间隔消息数
        private readonly timeLimit = 48;

        private scroll2EndWhenMessageLoaded = false;

        private preview = false;
        private imagePreview = {};

        private previewVideo = false;
        private videoPreview = {};

        @Ref("message-scrollbar")
        private scollbarElement!: Vue & {
            update: () => void;
        };

        private get scollWrapper(): HTMLElement | null {
            return this.scollbarElement?.$el?.firstChild as HTMLElement;
        }

        @Watch("messages")
        private whenHasMessages() {
            this.$nextTick(() => this.scollbarElement.update());
        }

        @Watch("preview")
        private onPreviewChanged() {
            if (!this.preview) {
                this.raiseFileOpen(false);
            }
        }

        @Watch("previewVideo")
        private onVideoPreviewChanged() {
            if (!this.previewVideo) {
                this.raiseFileOpen(false);
            }
        }

        @Watch("chatId")
        private onChatChanged(o: number, n: number) {
            o && n && this.messages.length
                ? this.fetchNewMsg()
                : setTimeout(() => this.fetchNewMsg(), 300);
            this.scroll2End(this.messages.length ? 0 : 100);
        }

        private raiseFileOpen(value: boolean) {
            this.$emit("file-open", value);
        }

        private get messageTimestampDictionary() {
            const dic = {} as { [prop: number]: boolean };
            let count = 0;

            if (this.historyMessage) {
                this.historyMessage.forEach((message, index, array) => {
                    if (
                        index === 0 ||
                        this.whetherShowTime(array[index - 1], message) ||
                        count === this.timeLimit - 1
                    ) {
                        dic[message.id] = true;
                        count = 0;
                    } else {
                        count++;
                    }
                });
            }

            return dic;
        }

        private loading = false;
        private loadingOld = false;
        private loadingNew = false;

        public created() {
            this.handleScrollWrapper();
            this.onNewMessage((e) => {
                if (e.type === MessageType.Withdraw) {
                    this.executeWithDraw(e.ref_id);
                    dbController
                        .removeMessage(e.chat_id, e.ref_id)
                        .finally(() => this.refresh());
                }
            });
        }

        public mounted() {
            this.scollWrapper &&
                this.scollWrapper.addEventListener("scroll", this.handleScroll);
            this.saveScrollToBottomFunc(this.scrollToNewMsg);
            this.scrollToNewMsg();
            setTimeout(() => this.scroll2End(200));
            setTimeout(() => this.scroll2End(1000), 200);
        }

        public beforeDestroy() {
            this.scollWrapper &&
                this.scollWrapper.removeEventListener("scroll", this.handleScroll);
            this.clearScrollToBottomFunc();
            this.clearNewMessage();
        }

        public scroll2End(delay?: number) {
            this.$nextTick(() => {
                const wrap = this.scollbarElement?.$el.querySelector(
                    ".el-scrollbar__wrap"
                ) as HTMLElement;
                if (wrap) {
                    if (delay) {
                        return setTimeout(
                            () =>
                                (wrap.scrollTop = Math.max(
                                    wrap.scrollHeight + 100,
                                    10000
                                )),
                            delay
                        );
                    }
                    wrap.scrollTop = Math.max(wrap.scrollHeight + 100, 10000);
                }
            });
        }

        private startLoading() {
            this.loading = true;
        }

        private endLoading() {
            this.loading = false;
        }

        private startLoadingOld() {
            this.startLoading();
            this.loadingOld = true;
        }

        private endLoadingOld() {
            this.endLoading();
            this.loadingOld = false;
        }

        private startLoadingNew() {
            this.startLoading();
            this.loadingNew = true;
        }

        private endLoadingNew() {
            this.endLoading();
            this.loadingNew = false;
        }

        private handleScroll!: () => void;
        private handleScrollWrapper() {
            let oldScrollTop = 0;
            this.handleScroll = () => {
                const wrapper = this.scollWrapper;
                const gap = 150;
                if (wrapper == null) return;
                const view = wrapper.firstChild as HTMLElement;
                const wrapperH = wrapper.getBoundingClientRect().height;
                const viewH = view.getBoundingClientRect().height;
                let scrollUp = false;
                let scrollDown = false;
                if (oldScrollTop > wrapper.scrollTop) {
                    scrollUp = true;
                    scrollDown = false;
                } else if (oldScrollTop < wrapper.scrollTop) {
                    scrollUp = false;
                    scrollDown = true;
                }
                this.forbidScrollTopToZero(wrapper);
                if (wrapper.scrollTop <= gap) {
                    scrollUp && this.fetchOldMsg();
                }
                if (wrapper.scrollTop - 40 - (viewH - wrapperH) >= -gap) {
                    scrollDown && this.fetchNewMsg();
                }
                oldScrollTop = wrapper.scrollTop;
            };
        }

        /* scrollTop为0时,新加载的消息后,滚动条也会保持在0的位置 */
        private forbidScrollTopToZero(ele: HTMLElement) {
            if (ele.scrollTop <= 10) {
                ele.scrollTop = 10;
            }
        }

        private scrollToNewMsg() {
            this.$nextTick(() => {
                if (this.loading) return;
                this.scroll2End();
            });
        }

        @throttle()
        private async fetchOldMsg() {
            if (this.loading) return;
            const msg = this.historyMessage;
            if (msg == null) return;
            if (msg.length === 0) return;
            this.startLoadingOld();
            const msgId = msg[0].id;
            const data = await this.getLastPageMsg(msgId);
            if (data.length === 0) {
                // eslint-disable-next-line no-console
                console.log("没有更多老消息了");
            }
            this.$emit("last-page", msgId);
            this.endLoadingOld();
        }

        @throttle()
        private async fetchNewMsg() {
            if (this.loading) return;
            const msg = this.historyMessage;
            if (msg == null) return;
            if (msg.length === 0) return;
            this.startLoadingNew();
            const msgId = getLastMessageId(msg);
            return this.getNextPageMsg(msgId)
                .then((data) => {
                    if (data.length === 0) {
                        // eslint-disable-next-line no-console
                        console.log("没有更多新消息了");
                    }
                    this.$emit("next-page", msgId);
                    this.endLoadingNew();
                })
                .catch((e) => {
                    if (
                        e &&
                        e.message &&
                        (e.message as string).includes("sql: no rows in result set")
                    ) {
                        this.setError(this.chatId as number);
                    }
                })
                .finally(() => this.endLoadingNew());
        }

        private format2Time(time: number) {
            return formatTime(time);
        }

        private whetherShowTime(previous: Message, current: Message) {
            return current.ts - previous.ts > 180;
        }

        private open(file: {
            type: string;
            msg: { url: string; name: string; size: number };
        }) {
            if (file.type === "image") {
                this.imagePreview = file.msg;
                this.preview = true;
                return this.raiseFileOpen(true);
            }

            if (file.type === "video") {
                this.videoPreview = file.msg;
                this.previewVideo = true;
                return this.raiseFileOpen(true);
            }
        }

        /**
         * 获取当期消息列表头尾消息的id
         */
        public getStart2EndMessageIds() {
            const v: { start: number; end: number } = { start: 0, end: 0 };
            if (this.historyMessage && this.historyMessage.length) {
                const start = this.historyMessage[0];
                v.start = start.id;
                const end = this.historyMessage[this.historyMessage.length - 1];
                v.end = end.id;
            }
            return v;
        }

        private refresh() {
            this.fetchNewMsg();
        }
    }
</script>

<style lang="less" scoped>
    .message-list {
        padding: 0 20px;
        padding-right: 0;
    }

    .loading-mask {
        height: 50px;
        line-height: 50px;
        text-align: center;
    }

    .message-template {
        &:first-child {
            padding-top: 10px;

            .timestamp {
                margin-top: 20px;
            }
        }

        &:last-child {
            padding-bottom: 10px;
        }

        .timestamp {
            font-size: 12px;
            user-select: none;
        }
    }
</style>