<!-- eslint-disable vue/no-v-html -->
<template>
  <ModuleBase ref="base" :type="data.type">
    <template #content>
      <!-- eslint-disable max-len -->
      <div class="fill-wrapper">
        <RichText
          v-if="property.isGetContent"
          ref="richText"
          v-model="data.content"
          :is-fill="true"
          toolbar="fontselect fontsizeselect forecolor backcolor | lineheight paragraphSpacing underline | bold italic strikethrough alignleft aligncenter alignright bullist numlist dotEmphasis"
          :wordlimit-num="false"
          :font-size="data?.unified_attrib?.font_size"
          :font-family="data?.unified_attrib?.font"
          :font-color="data?.unified_attrib?.text_color"
          @handleRichTextBlur="parsedContentPinyin"
        />
        <div v-if="data.property.fill_type === fillTypeList[1].value" class="select-vocabulary">
          <h5 class="title">选词列表：</h5>
          <el-button size="mini" @click="openAddWord">添加词汇</el-button>
          <ul class="word-list">
            <li v-for="(item, index) in data.word_list" :key="item.mark" class="word-item">
              <span v-html="sanitizeHTML(item.content)"></span>
              <el-button type="text" size="mini" class="delete-word" @click="removeWord(index)">
                <SvgIcon icon-class="delete-black" size="12" />
              </el-button>
            </li>
          </ul>
        </div>

        <span class="tips">在需要加空的内容处插入 3 个或以上的下划线“_”。</span>
        <div v-if="data.audio_file_id">
          <SoundRecord :wav-blob.sync="data.audio_file_id" />
        </div>
        <template v-else>
          <div :class="['upload-audio-play']">
            <UploadAudio
              v-if="data.property.audio_generation_method === 'upload'"
              :file-id="data.audio_file_id"
              :show-upload="!data.audio_file_id"
              @upload="uploads"
              @deleteFile="deleteFiles"
            />
            <div v-else-if="data.property.audio_generation_method === 'auto'" class="auto-matic" @click="handleMatic">
              <SvgIcon icon-class="voiceprint-line" class="record" />
              <span class="auto-btn">{{ data.audio_file_id ? '已生成' : '生成音频' }}</span
              >{{ data.audio_file_id ? '成功' : '' }}
            </div>
            <SoundRecord v-else :wav-blob.sync="data.audio_file_id" />
          </div>
        </template>

        <div>
          <el-button @click="parsedContentPinyin">识别</el-button>
          <el-button @click="openMultilingual">多语言</el-button>
        </div>

        <div v-if="data.answer.answer_list.length > 0" class="title">答案：</div>
        <div class="correct-answer">
          <el-input
            v-for="(item, i) in data.answer.answer_list.filter(({ type }) => type === 'any_one')"
            :key="item.mark"
            v-model="item.value"
            placeholder="多个答案可用‘/’分割"
            @blur="handleTone(item.value, i)"
          >
            <span slot="prefix">{{ i + 1 }}.</span>
          </el-input>
        </div>
      </div>

      <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">
        <span>拼音效果</span>
        <el-button
          v-show="isEnable(data.property.view_pinyin)"
          type="text"
          icon="el-icon-refresh"
          title="刷新"
          class="refresh-pinyin-btn"
          @click.native="parsedContentPinyin"
        />
      </el-divider>
      <template v-if="isEnable(data.property.view_pinyin)">
        <div v-for="(item, i) in data.model_essay" :key="i" class="pinyin-text-list">
          <PinyinText
            :key="`pinyin-${i}`"
            ref="PinyinText"
            :rich-text-list="item.rich_text_list"
            :pinyin-position="data.property.pinyin_position"
            :body-styles="getBodyStyles()"
            @fillCorrectPinyin="fillCorrectPinyin($event, i, -1, 'model_essay')"
          />
        </div>
      </template>

      <MultilingualFill
        :visible.sync="multilingualVisible"
        :text="data.content"
        :translations="data.multilingual"
        @SubmitTranslation="handleMultilingualTranslation"
      />
      <AnswerAnalysisList
        v-if="data.answer_list?.length > 0 || data.analysis_list?.length > 0"
        :answer-list="data.answer_list"
        :analysis-list="data.analysis_list"
        :unified-attrib="data.unified_attrib"
        @updateAnswerAnalysisFileList="updateAnswerAnalysisFileList"
        @deleteAnswerAnalysis="deleteAnswerAnalysis"
      />
      <AddWord :visible.sync="visibleWord" @add-word="addWord" />
    </template>
  </ModuleBase>
</template>

<script>
import ModuleMixin from '../../common/ModuleMixin';
import SoundRecord from '@/views/book/courseware/create/components/question/fill/components/SoundRecord.vue';
import UploadAudio from '@/views/book/courseware/create/components/question/fill/components/UploadAudio.vue';
import PinyinText from '@/components/PinyinText.vue';
import AddWord from './components/AddWord.vue';

import { getFillData, arrangeTypeList, fillTypeList } from '@/views/book/courseware/data/fill';
import { addTone, handleToneValue } from '@/views/book/courseware/data/common';
import { getRandomNumber } from '@/utils';
import { TextToAudioFile } from '@/api/app';
import { sanitizeHTML } from '@/utils/common';
import { PinyinBuild_OldFormat } from '@/api/book';

export default {
  name: 'FillPage',
  components: {
    SoundRecord,
    UploadAudio,
    PinyinText,
    AddWord,
  },
  mixins: [ModuleMixin],
  data() {
    return {
      data: getFillData(),
      fillTypeList,
      sanitizeHTML,
      visibleWord: false,
      rich_text_list: [],
    };
  },
  watch: {
    'data.property.arrange_type': 'handleMindMap',
    'data.property.fill_font': 'handleMindMap',
    'data.property': {
      handler() {
        this.parsedContentPinyin();
      },
      deep: true,
    },
  },
  methods: {
    async parsedContentPinyin() {
      PinyinBuild_OldFormat({
        text: this.data.content,
        is_first_sentence_first_hz_pinyin_first_char_upper_case:
          this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case,
        is_fill_space: 'true',
        is_rich_text: 'true',
      }).then(({ rich_text }) => {
        if (rich_text) {
          this.rich_text_list = rich_text.text_list;
          this.data.model_essay = this.parseRichText();
        }
      });
    },
    parseRichText() {
      let text_list = this.rich_text_list || [];
      const preservedAnyOneAnswers = (this.data.answer.answer_list || [])
        .filter(({ type }) => type === 'any_one')
        .map(({ value }) => value);
      const preservedAnyOneState = { index: 0 };
      this.data.answer.answer_list = [];
      const arr = [];
      let totalText = '';
      let totalRichText = [];
      const openStyleTagStack = [];
      for (let i = 0; i < text_list.length; i++) {
        const textItem = text_list[i];
        const text = textItem.text || '';
        const isStyle = textItem.is_style === 'true' || textItem.is_style === true;
        if (isStyle) {
          const isRichFill = /class=\s*(\\?["'])rich-fill\1/.test(text);
          if (isRichFill) {
            if (totalText.length > 0) {
              arr.push({
                content: totalText,
                type: 'text',
                rich_text_list: totalRichText,
              });
              totalText = '';
              totalRichText = [];
            }
            let nextData = text_list[i + 1] || {};
            let nextTag = text_list[i + 2] || {};
            const mark = getRandomNumber();
            arr.push({
              content: text + (nextData.text || '') + (nextTag.text || ''),
              type: 'input',
              input: '',
              audio_answer_list: [],
              mark,
              rich_text_list: [textItem, nextData, nextTag],
            });
            this.data.answer.answer_list.push({
              value: nextData.text || '',
              mark,
              type: 'only_one',
            });
            // 跳过下一个文本和标签数据
            i += 2;
          } else {
            totalText += text;
            totalRichText = totalRichText.concat(textItem);
            this.syncOpenStyleTagStack(openStyleTagStack, textItem);
          }
        } else {
          const splitBlocks = this.splitTextItemByUnderline(textItem, preservedAnyOneAnswers, preservedAnyOneState);

          const currentOpenStyleTags = openStyleTagStack.map((tagItem) => ({ ...tagItem }));
          const firstBlockPrefix =
            totalText.length > 0
              ? this.buildFirstBlockStylePrefix(totalRichText, currentOpenStyleTags)
              : currentOpenStyleTags;

          // 样式标签单独成块会导致 PinyinText 的样式栈断开，需并入紧随其后的文本块。
          if (splitBlocks.length > 0) {
            if (totalText.length > 0) {
              splitBlocks[0].content = `${totalText}${splitBlocks[0].content || ''}`;
            }

            splitBlocks.forEach((block, blockIndex) => {
              const prevRichTextList = block.rich_text_list || [];
              block.rich_text_list = [
                ...(blockIndex === 0 ? firstBlockPrefix : currentOpenStyleTags),
                ...prevRichTextList,
              ];
            });

            totalText = '';
            totalRichText = [];
          } else if (totalText.length > 0) {
            arr.push({
              content: totalText,
              type: 'text',
              rich_text_list: firstBlockPrefix,
            });
            totalText = '';
            totalRichText = [];
          }

          arr.push(...splitBlocks);
        }
      }

      if (totalText.length > 0) {
        arr.push({
          content: totalText,
          type: 'text',
          rich_text_list: totalRichText,
        });
      }

      return arr;
    },
    getStyleTagName(tagText = '') {
      const trimmedText = String(tagText).trim();
      const closeMatch = trimmedText.match(/^<\/(\w+)>$/);
      if (closeMatch) return closeMatch[1].toLowerCase();

      const openMatch = trimmedText.match(/^<(\w+)([^>]*)>$/);
      if (openMatch) return openMatch[1].toLowerCase();

      return '';
    },
    isOpenStyleTag(tagItem = {}) {
      const tagText = String(tagItem?.text || '').trim();
      const isStyleTag = tagItem?.is_style === 'true' || tagItem?.is_style === true;
      if (!isStyleTag) return false;
      if (/^<\//.test(tagText)) return false;
      if (/^<br\s*\/?\s*>$/i.test(tagText)) return false;
      return /^<\w+[^>]*>$/.test(tagText);
    },
    syncOpenStyleTagStack(openStyleTagStack, styleTagItem) {
      const tagText = String(styleTagItem?.text || '').trim();
      const isStyleTag = styleTagItem?.is_style === 'true' || styleTagItem?.is_style === true;
      if (!isStyleTag) return;
      if (/^<br\s*\/?\s*>$/i.test(tagText)) return;

      const closeMatch = tagText.match(/^<\/(\w+)>$/);
      if (closeMatch) {
        const closeTagName = closeMatch[1].toLowerCase();
        for (let i = openStyleTagStack.length - 1; i >= 0; i--) {
          const tagName = this.getStyleTagName(openStyleTagStack[i]?.text);
          if (tagName === closeTagName) {
            openStyleTagStack.splice(i, 1);
            break;
          }
        }
        return;
      }

      if (this.isOpenStyleTag(styleTagItem)) {
        openStyleTagStack.push(styleTagItem);
      }
    },
    buildFirstBlockStylePrefix(transitionStyleTags = [], currentOpenStyleTags = []) {
      const transitionOpenTagNames = transitionStyleTags
        .filter((tagItem) => this.isOpenStyleTag(tagItem))
        .map((tagItem) => this.getStyleTagName(tagItem?.text));

      const missingOpenTags = currentOpenStyleTags.filter((tagItem) => {
        const tagName = this.getStyleTagName(tagItem?.text);
        return tagName && !transitionOpenTagNames.includes(tagName);
      });

      return [...missingOpenTags, ...transitionStyleTags];
    },
    /**
     * 根据文本中的连续下划线分割文本块，并将下划线部分转换为输入块
     * @param {Object} textItem 富文本中的一个文本项
     * @param {Array} preservedAnyOneAnswers 预先保存的 any_one 类型答案列表
     * @param {Object} preservedAnyOneState any_one 答案的当前索引状态
     */
    splitTextItemByUnderline(textItem, preservedAnyOneAnswers = [], preservedAnyOneState = { index: 0 }) {
      const text = textItem?.text || '';
      const matcher = /_{3,}/g;
      const blocks = [];
      let lastIndex = 0;
      let match = matcher.exec(text);

      while (match) {
        const underlineText = match[0] || '';
        const start = match.index;
        const end = start + underlineText.length;

        if (start > lastIndex) {
          const textContent = text.slice(lastIndex, start);
          blocks.push({
            content: textContent,
            type: 'text',
            rich_text_list: [
              {
                ...textItem,
                text: textContent,
                word_list: this.sliceWordListByTextRange(textItem.word_list, lastIndex, start),
              },
            ],
          });
        }

        const mark = getRandomNumber();
        blocks.push({
          content: underlineText,
          type: 'input',
          input: '',
          audio_answer_list: [],
          mark,
          rich_text_list: [
            {
              ...textItem,
              text: underlineText,
              word_list: this.sliceWordListByTextRange(textItem.word_list, start, end),
            },
          ],
        });
        this.data.answer.answer_list.push({
          value: preservedAnyOneAnswers[preservedAnyOneState.index] || '',
          mark,
          type: 'any_one',
        });
        preservedAnyOneState.index += 1;

        lastIndex = end;
        match = matcher.exec(text);
      }

      if (lastIndex < text.length) {
        const textContent = text.slice(lastIndex);
        blocks.push({
          content: textContent,
          type: 'text',
          rich_text_list: [
            {
              ...textItem,
              text: textContent,
              word_list: this.sliceWordListByTextRange(textItem.word_list, lastIndex, text.length),
            },
          ],
        });
      }

      if (blocks.length === 0) {
        return [
          {
            content: text,
            type: 'text',
            rich_text_list: [textItem],
          },
        ];
      }

      return blocks;
    },
    sliceWordListByTextRange(wordList = [], rangeStart = 0, rangeEnd = 0) {
      if (!Array.isArray(wordList) || rangeEnd <= rangeStart) return [];

      const result = [];
      let cursor = 0;

      wordList.forEach((wordItem) => {
        const wordText = wordItem?.text || '';
        const wordStart = cursor;
        const wordEnd = wordStart + wordText.length;
        cursor = wordEnd;

        const overlapStart = Math.max(rangeStart, wordStart);
        const overlapEnd = Math.min(rangeEnd, wordEnd);

        if (overlapStart >= overlapEnd) return;

        const relativeStart = overlapStart - wordStart;
        const relativeEnd = overlapEnd - wordStart;

        result.push(this.sliceWordItem(wordItem, relativeStart, relativeEnd));
      });

      return result;
    },
    sliceWordItem(wordItem, start, end) {
      const fullText = wordItem?.text || '';
      const slicedText = fullText.slice(start, end);
      const slicedWord = {
        ...wordItem,
        text: slicedText,
      };

      const sourcePinyinList = Array.isArray(wordItem?.pinyin_list) ? wordItem.pinyin_list : [];

      if (sourcePinyinList.length === fullText.length) {
        slicedWord.pinyin_list = sourcePinyinList.slice(start, end);
        slicedWord.pinyin = (slicedWord.pinyin_list || []).join(' ').trim();
        return slicedWord;
      }

      if (start === 0 && end === fullText.length) {
        slicedWord.pinyin_list = [...sourcePinyinList];
        return slicedWord;
      }

      slicedWord.pinyin_list = new Array(slicedText.length).fill('');
      slicedWord.pinyin = '';
      return slicedWord;
    },
    /**
     * 识别文本中
     * @param {Boolean} isUpdatePinyin 是否更新拼音，默认为 true
     */
    identifyText(isUpdatePinyin = true) {
      this.data.answer.answer_list = [];
      const content = this.data.content || '';

      // 使用 class 为 rich-fill 的 span 以及连续 3 个及以上下划线作为分割符
      if (!content || !content.match(/<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>(.*?)<\/span>|_{3,}/gi)) {
        this.data.model_essay = [
          [
            {
              content,
              type: 'text',
              paragraph_list: [],
              paragraph_list_parameter: { text: '', pinyin_proofread_word_list: [] },
            },
          ],
        ];
        return;
      }

      const splitSource = content.split(/\n|<br>/).map((item) => {
        // rich-fill 和 ___ 均转为统一占位符，交给 splitRichText 处理
        return this.splitRichText(
          item.replace(/<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>.*?<\/span>|_{3,}/gi, '###$&###'),
        );
      });

      this.data.model_essay = splitSource;

      if (isUpdatePinyin) this.handleViewPinyin();
    },
    /**
     * 分割富文本
     * @param {String} str 富文本字符串
     * @returns {Array} 分割后的数组
     */
    splitRichText(str) {
      const parts = String(str).split(/###/g);
      const arr = [];

      for (let i = 0; i < parts.length; i++) {
        let content = parts[i] ?? '';

        // 偶数索引为普通文本段
        if (i % 2 === 0) {
          if (content === '') continue; // 跳过空文本块
          // 判断 content 最前面是否是标签
          const isStartWithTag = /^<[^>]+>/.test(content);

          if (!isStartWithTag) {
            content = this.setTag(i, parts, content);
          }

          arr.push({
            content,
            type: 'text',
            paragraph_list: [],
            paragraph_list_parameter: {
              text: '',
              pinyin_proofread_word_list: [],
            },
          });
          continue;
        }

        // 奇数索引为输入段（被 ### 包裹的分割符）
        const separatorContent = content;
        const isUnderline = /^_{3,}$/.test(separatorContent);
        const richFillMatch = separatorContent.match(/^<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>(.*?)<\/span>$/i);
        const answerValue = isUnderline ? '' : richFillMatch ? richFillMatch[1] : separatorContent;
        const mark = getRandomNumber();

        arr.push({
          content: separatorContent,
          type: 'input',
          input: '',
          audio_answer_list: [],
          mark,
          paragraph_list: [],
          paragraph_list_parameter: {
            text: '',
            pinyin_proofread_word_list: [],
          },
        });

        // 同步更新答案列表
        this.data.answer.answer_list.push({
          value: answerValue,
          mark,
          type: isUnderline ? 'any_one' : 'only_one',
        });
      }
      return arr;
    },

    /**
     * 设置前一个标签
     * @param {Number} index 当前索引
     * @param {Array} parts 分割后的数组
     * @param {String} content 当前内容
     * @returns {String} 包含向前两个标签内容中最后一个html标签的内容
     */
    setTag(index, parts, content) {
      let i = index;
      if (i < 2) return content;
      let _content = content;
      const isEndWithTag = /<\/[^>]+>$/.test(_content); // 判断是否以标签结尾
      let startTag = '';
      const part = parts[i - 2] ?? '';

      const tagMatch = part.match(/<[^>]+>/g);
      if (tagMatch) {
        startTag = tagMatch[tagMatch.length - 1]; // 获取最后一个标签
        // 如果是 <br> 标签，继续往前找，直到找到非 <br> 标签或者没有标签为止

        while (startTag.toLowerCase() === '<br>') {
          const prevPart = parts[i - 3] ?? '';
          const prevTagMatch = prevPart.match(/<[^>]+>/g);
          if (prevTagMatch === null) {
            startTag = '';
            break;
          }
          if (prevTagMatch) {
            startTag = prevTagMatch[prevTagMatch.length - 1];
            i -= 1;
          } else {
            break;
          }
        }
      }
      _content = `${startTag}${_content}`;
      if (!isEndWithTag) {
        let tag = startTag.match(/^<([^>\s]+).*?>/);
        tag = tag ? tag[1] : 'span';
        _content += `</${tag}>`;
      }
      return _content;
    },
    handleTone(value, i) {
      if (!/^[a-zA-Z0-9\s]+$/.test(value)) return;
      this.data.answer.answer_list[i].value = value
        .trim()
        .split(/\s+/)
        .map((item) => {
          return handleToneValue(item);
        })
        .map((item) =>
          item.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
        )
        .filter((item) => item.length > 0)
        .join(' ');
    },
    uploads(file_id) {
      this.data.audio_file_id = file_id;
    },
    deleteFiles() {
      this.data.audio_file_id = '';
    },
    // 自动生成音频
    handleMatic() {
      TextToAudioFile({
        text: this.data.content.replace(/<[^>]+>/g, ''),
        voice_type: this.data.property.voice_type,
        emotion: this.data.property.emotion,
        speed_ratio: this.data.property.speed_ratio,
      })
        .then(({ status, file_id }) => {
          if (status === 1) {
            this.data.audio_file_id = file_id;
          }
        })
        .catch(() => {});
    },
    /**
     * @description 处理思维导图数据
     */
    handleMindMap() {
      const { arrange_type } = this.data.property;
      const arrangeLabel = arrangeTypeList.find((item) => item.value === arrange_type)?.label || '';
      this.data.mind_map.node_list = [
        {
          name: `${arrangeLabel}填空组件`,
        },
      ];
    },
    handleViewPinyin() {
      if (!this.isEnable(this.data.property.view_pinyin)) {
        return;
      }

      this.data.model_essay.forEach((item, i) => {
        item.forEach((option, j) => {
          const text = option.content;
          option.paragraph_list_parameter.text = text;

          this.createParsedTextInfoPinyin(text, i, j, 'model_essay');
        });
      });
    },
    openAddWord() {
      this.visibleWord = true;
    },
    /**
     * 添加词汇
     * @param {string} word 词汇内容
     */
    addWord(word) {
      if (!word) return;
      this.data.word_list.push({
        content: word,
        mark: getRandomNumber(),
      });
    },
    removeWord(index) {
      this.data.word_list.splice(index, 1);
    },
    getBodyStyles() {
      if (!this.$refs.richText) return {};
      return this.$refs.richText.getBodyInitialStyles();
    },
  },
};
</script>

<style lang="scss" scoped>
.fill-wrapper {
  display: flex;
  flex-direction: column;
  row-gap: 16px;
  align-items: flex-start;

  :deep .rich-wrapper {
    width: 100%;
  }

  .select-vocabulary {
    .title {
      margin: 0 0 8px;
    }

    .word-list {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
      margin-top: 8px;

      .word-item {
        display: flex;
        gap: 6px;
        align-items: center;
        padding: 4px 6px;
        border: $border;
        border-radius: 4px;

        :deep p {
          margin: 0;
        }

        .delete-word {
          display: flex;
          align-items: center;
          padding: 0;
          line-height: 1;
        }
      }
    }
  }

  .tips {
    font-size: 12px;
    color: #999;
  }

  .auto-matic,
  .upload-audio-play {
    :deep .upload-wrapper {
      margin-top: 0;
    }

    .audio-wrapper {
      :deep .audio-play {
        width: 16px;
        height: 16px;
        color: #000;
        background-color: initial;
      }

      :deep .audio-play.not-url {
        color: #a1a1a1;
      }

      :deep .voice-play {
        width: 16px;
        height: 16px;
      }
    }
  }

  .auto-matic {
    display: flex;
    flex-shrink: 0;
    column-gap: 12px;
    align-items: center;
    width: 200px;
    padding: 5px 12px;
    background-color: $fill-color;
    border-radius: 2px;

    .auto-btn {
      font-size: 16px;
      font-weight: 400;
      line-height: 22px;
      color: #1d2129;
      cursor: pointer;
    }
  }

  .correct-answer {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;

    .el-input {
      width: 180px;

      :deep &__prefix {
        display: flex;
        align-items: center;
        color: $text-color;
      }
    }
  }
}

.pinyin-text-list {
  display: inline;

  :deep .pinyin-area {
    display: inline;
  }

  :deep .pinyin-area .rich-text-container,
  :deep .pinyin-area .pinyin-paragraph {
    display: inline;
  }
}
</style>
