<template>
    <Split v-model="split">
        <template #left>
            <MdEditor ref="editorRef" v-model="editorState.text" :id="editorState.id" :theme="editorState.theme"
                :previewTheme="editorState.previewTheme" :style="{ height: editorState.height }"
                :toolbars="editorState.toolbars" :toolbarsExclude="editorState.toolbarsExclude" @onSave="onSave"
                @onHtmlChanged="getPreviewedHTML" @onUploadImg="onUploadImg">
                <template #defToolbars>
                    <NormalToolbar :title="leftSidebarState == 'open' ? '隐藏文件树' : '显示文件树'" @onClick="toggleLeftSideBar">
                        <template #trigger>
                            <Icon v-if="leftSidebarState == 'open'" type="ios-arrow-dropleft" size="22" />
                            <Icon v-else type="ios-arrow-dropright" size="22" />
                        </template>
                    </NormalToolbar>
                    <NormalToolbar :title="split == 1 ? '显示目录' : '隐藏目录'" @onClick="toggleCatalog">
                        <template #trigger>
                            <Icon :class="split == 1 ? '' : 'md-editor-toolbar-active'" type="ios-list-box-outline"
                                size="22" />
                        </template>
                    </NormalToolbar>
                    <ExportPDF :modelValue="editorState.text" :customize="customizePDF"
                        @onProgress="handleExportProgress" />
                    <NormalToolbar title="发布文章" @onClick="publish">
                        <template #trigger>
                            <Icon class="md-editor-icon" type="md-cloud-upload" size="22" />
                        </template>
                    </NormalToolbar>
                    <ThemeSwitch v-model="editorState.theme" />
                    <PreviewThemeSwitch v-model="editorState.previewTheme" />
                </template>
            </MdEditor>
        </template>
        <template #trigger>
            <div class="split-trigger"></div>
        </template>
        <template #right>
            <div class="split-right md-catalog">
                <MdCatalog :class="hiddenSplitRight" :editorId="editorState.id" :theme="editorState.theme" />
            </div>
        </template>
    </Split>
    <UploadImageConfig v-model:visible="uploadImgForm.showFormModal" v-model:uploadImgAPI="uploadImgForm.uploadImgAPI"
        v-model:uploadImgField="uploadImgForm.uploadImgField" v-model:otherField="uploadImgForm.otherField">
    </UploadImageConfig>
</template>

<script setup>
import 'md-editor-v3/lib/style.css';
import '@vavt/v3-extension/lib/asset/PreviewThemeSwitch.css';
import '@vavt/v3-extension/lib/asset/ExportPDF.css';
import UploadImageConfig from './UI/UploadImageConfig.vue';
import scriptjs from 'scriptjs'
import { config, MdEditor, MdCatalog, NormalToolbar } from 'md-editor-v3';
import { ThemeSwitch, PreviewThemeSwitch, ExportPDF } from '@vavt/v3-extension';
import { lineNumbers } from '@codemirror/view';
import { ref, reactive, watch, nextTick, onMounted } from "vue";
import { Message } from 'view-ui-plus'

const props = defineProps({
    markdownCode: {
        type: String,
        required: false,
        default: ''
    },
    leftSidebarState: {
        type: String,
        required: false,
        default: 'open'
    },
    save: {
        type: Function,
        required: false,
        default: (markdownCode) => {
            console.log("save markdown code: " + markdownCode);
        }
    },
});

const emit = defineEmits([
    'update:markdownCode',
    'update:leftSidebarState',
    'exportPDF',
]);

// 定义布局，主要用于在右边显示文章目录
const split = ref(1);
const defaultCatalogSplit = 0.2; // 默认目录和编辑器的比例为 8:2
const hiddenSplitRight = split.value == 1 ? ref('hidden') : ref('');
watch(split, (newSplit) => {
    if (newSplit > 0.95) {
        split.value = 1
        if (hiddenSplitRight.value == '') {
            hiddenSplitRight.value = 'hidden'
        }
    } else {
        if (hiddenSplitRight.value == 'hidden') {
            hiddenSplitRight.value = ''
        }
    }
})

// 加载 Markdown 编辑器
const editorRef = ref(null);
const editorState = reactive({
    id: 'markdown-editor',
    theme: 'light',
    previewTheme: 'default',
    height: '100vh',
    text: props.markdownCode,
    toolbars: [
        0,
        1,
        '-',
        'bold',
        'underline',
        'italic',
        '-',
        'title',
        'strikeThrough',
        'sub',
        'sup',
        'quote',
        'unorderedList',
        'orderedList',
        'task',
        '-',
        'codeRow',
        'code',
        'link',
        'image',
        'table',
        'mermaid',
        'katex',
        '-',
        'revoke',
        'next',
        'save',
        2,
        3,
        '=',
        'prettier',
        'pageFullscreen',
        'fullscreen',
        'preview',
        'previewOnly',
        'htmlPreview',
        'catalog',
        'github',
        4,
        5,
    ],
    toolbarsExclude: ['fullscreen', 'github'], // 工具栏排除全屏和 github 等
    pdfIns: null, // 用于存储 PDF 导出时候的 jsPDF 实例
});
// 监听 markdownCode（由父组件更新） 和 text（本组件更新）以实现双向更新
watch(() => props.markdownCode, (newCode) => {
    // 当外部传入的 markdownCode 变化时候，更新 text 值
    editorState.text = newCode;
})
watch(() => editorState.text, (newCode) => {
    // 当 text 值变化时，告诉父组件
    emit('update:markdownCode', newCode);
})

config({
    codeMirrorExtensions(_theme, extensions) {
        // 编辑器显示输入框的行号
        return [...extensions, lineNumbers()];
    },
});

const toggleLeftSideBar = () => {
    if (props.leftSidebarState == 'open') {
        emit('update:leftSidebarState', 'close');
    } else {
        emit('update:leftSidebarState', 'open');
    }
}

const toggleCatalog = () => {
    if (split.value < 1) {
        split.value = 1;
    } else {
        split.value = 1 - defaultCatalogSplit;
    }
}

const onSave = (v, h) => {
    // 调用 save 方法保存代码（参数 v 为 markdown code）
    props.save(v);

    // 使用 h 可以在保存时直接获取 html
    // h.then((html) => {
    //     console.log(html);
    // });
};

const getPreviewedHTML = (html) => {
    console.log(html);
}

const customizePDF = (ins) => {
    // 将 jsPDF 的实例记录到 editorState.pdfIns 便于后面获取
    editorState.pdfIns = ins;
}

const handleExportProgress = (progress) => {
    // console.log(`Export progress: ${progress.ratio * 100}%`);
    // 当进度到达 100% 时，触发保存事件。
    if (progress.ratio == 1) {
        // 对于浏览器环境，ExportPDF 插件中已实现调用保存文件的窗口
        if (window.__TAURI_INTERNALS__ === undefined) {
            return;
        }
        // 非浏览器环境（tauri 环境）需要主动调用系统 API，调出对话框并保存文件
        if (editorState.pdfIns !== null) {
            emit('exportPDF', editorState.pdfIns.output('arraybuffer'));
        }
    }
};

const publish = () => {
    // TODO 调用接口将 markdown 源码或者 HTML 代码发布到云端
    Message.warning('抱歉，该功能暂未开放');
}

const uploadImgForm = reactive({
    // 是否显示表单对话框
    showFormModal: false,
    // 上传图片的后端 API 接口
    uploadImgAPI: localStorage.getItem('imageUploadURLOfEditor') || '',
    // 上传图片时，FormData 的文件字段
    uploadImgField: localStorage.getItem('imageUploadFieldOfEditor') || '',
    // 上传图片时，FormData 里附带的其他字段和数值. example: [{ key: 'token', value: 'uuid' }]
    otherField: JSON.parse(localStorage.getItem('imageUploadOtherFieldOfEditor')) || [],
});
watch(() => uploadImgForm.uploadImgAPI, (newAPI) => {
    localStorage.setItem('imageUploadURLOfEditor', newAPI);
})
watch(() => uploadImgForm.uploadImgField, (newField) => {
    localStorage.setItem('imageUploadFieldOfEditor', newField);
})
watch(() => uploadImgForm.otherField, (newData) => {
    localStorage.setItem('imageUploadOtherFieldOfEditor', JSON.stringify(newData));
})

const onUploadImg = async (files, callback) => {
    if (uploadImgForm.uploadImgAPI.trim().length == 0 || uploadImgForm.uploadImgField.trim().length == 0) {
        Message.warning("请先配置图片上传的后端接口，以及对应的字段名称");
        uploadImgForm.showFormModal = true;
        return;
    }

    const res = await Promise.all(
        files.map((file) => {
            return new Promise((rev, rej) => {
                const formData = new FormData();
                formData.append(uploadImgForm.uploadImgField.trim(), file);
                uploadImgForm.otherField.forEach(field => {
                    formData.append(field.key.trim(), field.value);
                });

                fetch(uploadImgForm.uploadImgAPI.trim(), {
                    method: 'POST',
                    body: formData,
                }).then(response => {
                    if (!response.ok) throw new Error(`HTTP 错误: ${response.status}`);
                    return response.json();
                }).then(data => {
                    rev(data);
                }).catch(error => {
                    rej(error);
                    Message.error(`上传失败: ${error}`);
                });
            });
        })
    );

    callback(res.map((item) => item.url));
};

// 用于将其他 JS 脚本加载进来
const fetchScript = (url) => {
    return new Promise((resolve) => {
        scriptjs(url, () => {
            resolve()
        })
    })
}

onMounted(async () => {
    // 加载 jQuery 脚本以便能够使用 jQuery 语法
    await fetchScript('/static/jquery/jquery.min.js');
    // 使用 nextTick 确保页面组件已完成挂载和渲染
    await nextTick();

    // （这个做法并不规范）找到工具栏中「上传图片」的下拉框，新增一个选项，支持更新图片上传的配置
    let imageIcon = $('div.md-editor-toolbar-item[title="图片"]').first();
    let dropdownOverlay = imageIcon.next().children().first();
    let ulOfDropdownOverlay = dropdownOverlay.children().first();
    let updateLi = $('<li class="md-editor-menu-item md-editor-menu-item-image" role="menuitem" tabindex="0">更新配置</li>');
    updateLi.on('click', () => uploadImgForm.showFormModal = true);
    updateLi.appendTo(ulOfDropdownOverlay);
});

// 对父组件暴露数据或方法
defineExpose({})
</script>

<style scoped>
.md-catalog {
    height: 100vh;
    overflow-x: hidden;
    overflow-y: auto;
    scrollbar-width: none;
}
</style>