<!-- eslint-disable vue/no-v-html -->
<template>
  <div class="select-preview" :style="[getAreaStyle(), getComponentStyle()]">
    <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />

    <div class="main" :style="getMainStyle()">
      <AudioFill
        v-if="data.audio_file_id.length > 0"
        :color="data.unified_attrib?.topic_color"
        :file-id="data.audio_file_id"
      />
      <div class="fill-wrapper">
        <p>
          <template v-for="(li, i) in modelEssay">
            <template v-if="li.type === 'text'">
              <PinyinText
                :key="`text-${i}`"
                class="content"
                :paragraph-list="li.paragraph_list"
                :rich-text-list="li.rich_text_list"
                :pinyin-position="data.property.pinyin_position"
                :body-styles="pinyinBodyStyles"
                :is-show-pinyin="isEnable(data.property.view_pinyin)"
                :is-preview="true"
              />
            </template>
            <template v-if="li.type === 'input'">
              <!-- 输入填空 -->
              <template v-if="data.property.fill_type === fillTypeList[0].value">
                <el-input
                  :key="`input-${i}`"
                  :ref="`input-${li.mark}`"
                  v-model="li.input"
                  :disabled="disabled"
                  :class="[...computedAnswerClass(li.mark)]"
                  :style="[
                    {
                      fontFamily: data.property.fill_font,
                      width: (inputWidthMap[li.mark] || data.property.input_default_width) + 'px',
                    },
                  ]"
                  @input="handleInput(li.input, li.mark)"
                />
              </template>

              <!-- 选词填空 -->
              <template v-else-if="data.property.fill_type === fillTypeList[1].value">
                <el-popover :key="`popover-${i}`" placement="top" trigger="click">
                  <div class="word-list">
                    <span
                      v-for="{ content, mark } in data.word_list"
                      :key="mark"
                      class="word-item"
                      @click="handleSelectWord(content, mark, li)"
                      v-html="sanitizeHTML(content)"
                    >
                    </span>
                  </div>

                  <span
                    slot="reference"
                    class="select-content"
                    :style="[{ minWidth: data.property.input_default_width + 'px' }]"
                    v-html="sanitizeHTML(li.input)"
                  ></span>
                </el-popover>
              </template>

              <!-- 手写填空 -->
              <template v-else-if="data.property.fill_type === fillTypeList[2].value">
                <span :key="`write-${i}`" class="write-click" @click="handleWriteClick(li.mark)">
                  <img
                    v-show="li.write_base64"
                    style="background-color: #f4f4f4"
                    :src="li.write_base64"
                    alt="write-show"
                  />
                </span>
              </template>

              <!-- 语音填空 -->
              <template v-else-if="data.property.fill_type === fillTypeList[3].value">
                <SoundRecordBox
                  ref="record"
                  :key="`record-${i}`"
                  type="mini"
                  :many-times="false"
                  class="record-box fill"
                  :attrib="data.unified_attrib"
                  :answer-record-list="data.audio_answer_list"
                  :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
                  @handleWav="handleMiniWav($event, li.mark)"
                />
              </template>
            </template>
          </template>
        </p>
      </div>
      <SoundRecord
        v-if="isEnable(data.property.is_enable_voice_answer)"
        ref="record"
        type="normal"
        class="record-box"
        :attrib="data.unified_attrib"
        :answer-record-list="data.record_list"
        :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
        @handleWav="handleWav"
      />
      <div v-if="showLang" class="lang">
        {{ data.multilingual.find((item) => item.type === getLang())?.translation }}
      </div>
      <PreviewOperation @showAnswerAnalysis="showAnswerAnalysis" @judgeCorrect="judgeCorrect" @retry="retry" />
    </div>

    <WriteDialog :visible.sync="writeVisible" @confirm="handleWriteConfirm" />
    <AnswerCorrect
      :visible.sync="visibleAnswerCorrect"
      :is-check-correct="isCheckCorrect"
      @closeAnswerCorrect="closeAnswerCorrect"
    />
    <AnswerAnalysis
      :visible.sync="visibleAnswerAnalysis"
      :answer-list="data.answer_list"
      :analysis-list="data.analysis_list"
      @closeAnswerAnalysis="closeAnswerAnalysis"
    >
      <div slot="right-answer" class="fill-wrapper">
        <p>
          <template v-for="(li, i) in modelEssay">
            <template v-if="li.type === 'text'">
              <PinyinText
                :key="`answer-text-${i}`"
                :is-show-pinyin="isEnable(data.property.view_pinyin)"
                class="content"
                :paragraph-list="li.paragraph_list"
                :rich-text-list="li.rich_text_list"
                :body-styles="pinyinBodyStyles"
                :pinyin-position="data.property.pinyin_position"
                :is-preview="true"
              />
            </template>
            <template v-if="li.type === 'input'">
              <span v-show="computedAnswerText(li.mark).length > 0" :key="`answer-${i}`" class="right-answer">
                {{ computedAnswerText(li.mark) }}
              </span>
            </template>
          </template>
        </p>
      </div>
    </AnswerAnalysis>
  </div>
</template>

<script>
import PreviewMixin from '../common/PreviewMixin';
import AudioFill from './components/AudioFillPlay.vue';
import SoundRecord from '../../common/SoundRecord.vue';
import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
import WriteDialog from './components/WriteDialog.vue';

import { getFillData, fillTypeList, arrangeTypeList, audioPositionList } from '@/views/book/courseware/data/fill';

export default {
  name: 'FillPreview',
  components: {
    AudioFill,
    SoundRecord,
    SoundRecordBox,
    WriteDialog,
  },
  mixins: [PreviewMixin],
  data() {
    return {
      data: getFillData(),
      fillTypeList,
      modelEssay: [],
      inputWidthMap: {},
      selectedWordList: [], // 用于存储选中的词汇
      writeVisible: false,
      writeMark: '',
    };
  },
  computed: {
    pinyinBodyStyles() {
      const unifiedAttrib = this.data?.unified_attrib || {};
      return {
        fontFamily: unifiedAttrib.font || undefined,
        fontSize: unifiedAttrib.font_size || undefined,
        color: unifiedAttrib.text_color || undefined,
      };
    },
  },
  watch: {
    'data.model_essay': {
      handler(list) {
        if (!list || !Array.isArray(list)) return;

        this.modelEssay = JSON.parse(JSON.stringify(list));
        this.syncAnswerList(list);
      },
      deep: true,
      immediate: true,
    },
    modelEssay: {
      handler(list) {
        if (!list || !Array.isArray(list)) return;

        this.syncAnswerList(list);
      },
      deep: true,
      immediate: true,
    },
    isJudgingRightWrong(val) {
      if (!val) return;

      this.answer.answer_list.forEach(({ mark, value }) => {
        const li = this.modelEssay.find((item) => item.mark === mark);
        if (li) {
          li.input = value;
        }
      });

      this.handleWav(this.answer.record_list);
    },
    'data.record_list'(val) {
      this.answer.record_list = val;
    },
  },
  methods: {
    handleWav(data) {
      this.data.record_list = data;
    },
    syncAnswerList(list) {
      this.answer.answer_list = list
        .map(({ type, input, audio_answer_list, mark }) => {
          if (type === 'input') {
            return {
              value: input,
              mark,
              audio_answer_list,
              write_base64: '',
            };
          }
          return null;
        })
        .filter((item) => item);
    },
    /**
     * 处理小音频录音
     * @param {Object} data 音频数据
     * @param {String} mark 选项标识
     */
    handleMiniWav(data, mark) {
      if (!data || !mark) return;
      const li = this.modelEssay.find((item) => item?.mark === mark);
      if (li) {
        this.$set(li, 'audio_answer_list', data);
      }
    },
    /**
     * 处理选中词汇
     * @param {String} content 选中的词汇内容
     * @param {String} mark 选项标识
     * @param {Object} li 当前输入框对象
     */
    handleSelectWord(content, mark, li) {
      if (!content || !mark || !li) return;

      li.input = content;
      this.selectedWordList.push(mark);
    },
    /**
     * 处理书写区确认
     * @param {String} data 书写区数据
     */
    handleWriteConfirm(data) {
      if (!data) return;
      const li = this.modelEssay.find((item) => item?.mark === this.writeMark);
      if (li) {
        this.$set(li, 'write_base64', data);
      }
    },
    handleWriteClick(mark) {
      this.writeVisible = true;
      this.writeMark = mark;
    },
    getMainStyle() {
      const isRow = this.data.property.arrange_type === arrangeTypeList[0].value;
      const isFront = this.data.property.audio_position === audioPositionList[0].value;
      const isEnableVoice = this.data.property.is_enable_voice_answer === 'true';
      const isHasAudio = this.data.audio_file_id.length > 0;
      let areaList = [
        { name: 'audio', value: '24px' },
        { name: 'fill', value: '1fr' },
      ];

      if (!isHasAudio) {
        areaList.shift();
      }

      if (!isFront && isHasAudio) {
        areaList = areaList.reverse();
      }

      let gridArea = '';
      let gridTemplateRows = '';
      let gridTemplateColumns = '';

      if (isRow) {
        const rowAreas = areaList.map(({ name }) => name);
        const rowCols = areaList.map(({ value }) => value);
        const templateRows = ['auto'];

        if (isEnableVoice) {
          rowAreas.push('record');
          rowCols.push('160px');
        }

        const areaRows = [`"${rowAreas.join(' ')}"`];

        if (this.showLang) {
          areaRows.push(`"${Array(rowAreas.length).fill('lang').join(' ')}"`);
          templateRows.push('auto');
        }

        gridArea = areaRows.join(' ');
        gridTemplateRows = templateRows.join(' ');
        gridTemplateColumns = rowCols.join(' ');
      } else {
        const areaRows = areaList.map(({ name }) => `"${name}"`);
        const templateRows = areaList.map(({ value }) => value);

        if (isEnableVoice) {
          areaRows.push('"record"');
          templateRows.push('32px');
        }

        if (this.showLang) {
          areaRows.push('"lang"');
          templateRows.push('auto');
        }

        gridArea = areaRows.join(' ');
        gridTemplateRows = templateRows.join(' ');
        gridTemplateColumns = '1fr';
      }

      return {
        'grid-auto-flow': isRow ? 'column' : 'row',
        'column-gap': isRow && isHasAudio ? '16px' : undefined,
        'row-gap': isRow || !isHasAudio ? undefined : '8px',
        'grid-template-areas': gridArea,
        'grid-template-rows': gridTemplateRows,
        'grid-template-columns': gridTemplateColumns,
      };
    },
    /**
     * 计算答题对错选项字体颜色
     * @param {string} mark 选项标识
     */
    computedAnswerClass(mark) {
      if (!this.isJudgingRightWrong && !this.isShowRightAnswer) {
        return '';
      }
      let selectOption = this.answer.answer_list.find((item) => item.mark === mark);
      let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
      if (!selectOption || !answerOption) return '';
      let selectValue = selectOption.value;
      let answerValue = answerOption.value;
      let answerType = answerOption.type;
      let classList = [];
      let isRight =
        answerType === 'only_one' ? selectValue === answerValue : answerValue.split('/').includes(selectValue);
      if (this.isJudgingRightWrong) {
        isRight ? classList.push('right') : classList.push('wrong');
      }

      if (this.isShowRightAnswer && !isRight) {
        classList.push('show-right-answer');
      }
      return classList;
    },
    /**
     * 计算正确答案文本
     * @param {string} mark 选项标识
     */
    computedAnswerText(mark) {
      let answerOption = this.data.answer.answer_list.find((item) => item.mark === mark);
      if (!answerOption) return '';
      let answerValue = answerOption.value;
      return `${answerValue}`;
    },
    // 重做
    retry() {
      this.modelEssay.forEach((li) => {
        if (li.type === 'input') {
          li.input = '';
          li.write_base64 = '';
        }
      });
      this.selectedWordList = [];
      this.isJudgingRightWrong = false;
      this.isShowRightAnswer = false;
      this.handleWav([]);
    },
    /**
     * 获取无文本内容的数据结构，用于保存为个人模板时的样式模板
     */
    getNoTextContentData() {
      let noTextContentData = JSON.parse(JSON.stringify(this.data));
      const resetFieldMap = {
        model_essay: [],
        content: '',
        audio_file_id: '',
        file_list: [],
        multilingual: [],
        analysis_list: [],
        answer_list: [],
        record_list: [],
      };

      Object.assign(noTextContentData, resetFieldMap);

      if (noTextContentData.answer) {
        noTextContentData.answer.answer_list = [];
        noTextContentData.answer.reference_answer = '';
      }

      return noTextContentData;
    },
    /**
     * 处理输入框内容变化
     * @description 根据输入框值计算所需长度，动态调整输入框宽度，输入框宽度不小于默认宽度，且不超过组件最大宽度 1000px，需要考虑输入框前面的宽度，保证输入框不会超出组件范围
     * @param {String} value 输入框当前值
     * @param {String} mark 选项标识
     */
    handleInput(value, mark) {
      if (!mark) return;

      const text = `${value || ''}`;
      const defaultWidth = Number(this.data?.property?.input_default_width) || 120;
      const fontSize = 16;
      const fontFamily = this.data?.property?.fill_font || 'arial';

      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      let textWidth = 0;

      if (context) {
        context.font = `${fontSize}pt ${fontFamily}`;
        textWidth = context.measureText(text || ' ').width;
      }

      // 额外留出输入框内边距和边框余量，避免刚好卡边。
      const contentWidth = Math.ceil(textWidth + 24);

      const inputRef = this.$refs[`input-${mark}`];
      const inputVm = Array.isArray(inputRef) ? inputRef[0] : inputRef;
      const inputEl = inputVm?.$el;
      const containerEl = this.$el;

      let availableWidth = 1000;
      if (inputEl && containerEl) {
        const inputRect = inputEl.getBoundingClientRect();
        const containerRect = containerEl.getBoundingClientRect();
        const rightLimit = containerRect.left + Math.min(containerRect.width, 1000);
        availableWidth = Math.floor(rightLimit - inputRect.left - 12);
      }

      const maxWidth = Math.max(40, availableWidth);
      const nextWidth = Math.min(Math.max(contentWidth, defaultWidth), maxWidth);

      this.$set(this.inputWidthMap, mark, nextWidth);
    },
  },
};
</script>

<style lang="scss" scoped>
@use '@/styles/mixin.scss' as *;

.select-preview {
  @include preview-base;

  .main {
    display: grid;
    gap: 10px;
    align-items: center;
  }

  .fill-wrapper {
    grid-area: fill;
    font-size: 16pt;

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

    p {
      margin: 0;
    }

    .content {
      display: inline;
    }

    .record-box.fill {
      display: inline-flex;
      align-items: center;
      background-color: #fff;
      border-bottom: 1px solid $font-color;

      :deep(.playBack) {
        width: 20px;
        height: 20px;
      }
    }

    .write-click {
      display: inline-block;
      width: 104px;
      height: 32px;
      padding: 0 4px;
      vertical-align: bottom;
      cursor: pointer;
      border-bottom: 1px solid $font-color;

      img {
        width: 100%;
        height: 100%;
      }
    }

    .select-content {
      display: inline-block;
      height: 32px;
      margin: 0 10px;
      vertical-align: bottom;
      cursor: pointer;
      border-bottom: 1px solid $font-color;

      :deep p {
        margin: 0;
      }
    }

    .el-input {
      display: inline-flex;
      align-items: center;
      width: 120px;
      margin: 0 10px;
      vertical-align: bottom;

      &.pinyin :deep input.el-input__inner {
        font-family: 'PINYIN-B', sans-serif;
      }

      &.chinese :deep input.el-input__inner {
        font-family: 'arial', sans-serif;
      }

      &.english :deep input.el-input__inner {
        font-family: 'arial', sans-serif;
      }

      &.right {
        :deep input.el-input__inner {
          color: $right-color;
        }
      }

      &.wrong {
        :deep input.el-input__inner {
          color: $error-color;
        }
      }

      :deep .el-input__inner {
        padding: 0;
        font-size: 16pt;
        color: $font-color;
        text-align: center;
        background-color: #fff;
        border-width: 0;
        border-bottom: 1px solid $font-color;
        border-radius: 0;
      }
    }

    .right-answer {
      position: relative;
      display: inline-block;
      height: 32px;
      margin: 0 4px;
      line-height: 32px;
      vertical-align: bottom;
      border-bottom: 1px solid $font-color;
    }
  }

  .record-box {
    padding: 6px 12px;
    background-color: $fill-color;

    :deep .record-time {
      width: 100px;
    }
  }
}
</style>

<style lang="scss" scoped>
.word-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;

  .word-item {
    cursor: pointer;
  }
}
</style>
