/**
 * 格式化分类列表
 * @param {Array} arr 分类数据(包含Children的二级树形数据)
 * @param {String} parentIdName 一级节点Id名称,作为tree的nodeKeyId
 * @param {String} itemIdName 二级节点Id名称
 * @returns Array
 */
export const formatArr = (arr,parentIdName='SaDocClassifyId',itemIdName='SaDocId') => {
  let tempArr = arr.map((item) => {
    item.label = item.ClassifyName
    item.nodeKeyId = item[parentIdName] + ''
    item.children = item.Children ? item.Children.map((i) => {
      i.label = i.Title
      i.nodeKeyId = 'children_' + i[itemIdName]
      i.parentId = item[parentIdName]
      if(item.CoverImg){
        i.CoverImg = item.CoverImg
      }
      return i
    }) : []
    return item
  })
  return tempArr
}

/* 文档渲染相关 */

/**
 * 获取段落的纯文本
 * @param {Object} section 段落数据 
 * @param {String} section.Content 段落内容（富文本）
 * @returns String
 */
export const formatFileContent = (section) => {
  const tempdiv = document.createElement('div')
  tempdiv.innerHTML = section.Content
  return tempdiv.innerText
}
/**
 * 从标签数组中筛选出对应分类的标签
 * @param {Array} LabelList 段落已打标签
 * @param {String} type 分类标签名
 * @returns Array
 */
export const formatFileLabel = (LabelList, type) => {
  const labelList = LabelList.filter(label => {
    return label[type] === 1
  })
  return labelList
}
/**
 * 判断selectArr与arr是否有重合的项
 * @param {Array} selectArr 
 * @param {Array} arr 
 * @returns Boolean
 */
export const checkSelectedLabel = (selectArr, arr) => {
  const selectArrIds = selectArr.map(i => {
    return i.LabelId
  })
  const arrIds = arr.map(i => {
    return i.LabelId
  })
  if(arrIds.length===0||selectArrIds.length === 0) return false
  const concatIds = new Set(selectArrIds.concat(arrIds))
  return !(concatIds.size === (arrIds.length + selectArrIds.length))
}
/**
 * 判断数组内的每一项是否有与obj相等的
 * @param {Array} list 
 * @param {Object} obj 
 * @returns 
 */
export const isTextEqual = (list, obj) => {
    let flag = false
    for (let i = 0; i < list.length; i++) {
        if (_.isEqual(list[i], obj)) {
            flag = true
            break

        }
    }
    return flag
}
/**
 * 获取文档的纯文本
 * @param {Object} file 
 * @param {Array} file.SectionList
 * @returns 
 */
export const formatFile = (file)=>{
  file.SectionList = file.SectionList.map(section=>{
    section.innerText = formatFileContent(section)
    return section
  })
  return file
}
//格式化语义分析文档，格式化标签信息
/*
  具体操作如下:
  遍历file.SectionList,根据IsPart将其分离成两个list SectionList和textAreaList
  段落List(SectionList)
    1.获取段落纯文本
    2.根据LabelList,获取isThisLabelArr,isHistoryLabelArr,isOtherLabelArr三个数组
    3.若获取isThisLabelArr有值,则ishasThisLabel为true
    4.将2中的3个数组与selectArr比较,若有一个重合,则isSelectLabel为true
    (isThisLabelArr 该语义分析内该段落被打的当前标签 isHistoryLabelArr 该段落被操作者打的历史标签 isOtherLabelArr该段落被打的其他人标签)
  片段(选定文字)List(textAreaList)
    1 格式与section基本相同,增加了startOffset(文字在段落的开始下标)和endOffset(文字在段落的结束下标),用于找到回显的Range
    2 每一项在页面渲染完成后会增加一个属性areaRangeInfo 格式参考config/areaRangeInfo 和 getTextRange
 */
/**
 * 格式化语义分析文档，格式化标签信息
 * @param {Object} file 
 * @param {Array} selectArr 
 * @returns Object
 */
export const formatSemanticFile = (file,selectArr)=>{
  let temp = _.cloneDeep(file)
  let textAreaList = []
  temp.SectionList = temp.SectionList.map(item => {
    const {LabelList} = item
    if(!LabelList){
      item.LabelList = []
    }
    item.innerText = formatFileContent(item)
    item.isThisLabelArr = formatFileLabel(LabelList || [], 'IsThis')
    item.isHistoryLabelArr = formatFileLabel(LabelList || [], 'IsHistory')
    item.isOtherLabelArr = formatFileLabel(LabelList || [], 'IsOther')
    item.ishasThisLabel = item.isThisLabelArr.length ? true : false
    item.isSelectLabel = checkSelectedLabel(selectArr, item.isHistoryLabelArr) ||
      checkSelectedLabel(selectArr, item.isOtherLabelArr)||checkSelectedLabel(selectArr,item.isThisLabelArr)
    if (item.IsPart === 1) {
        const tempObj = {
                ...item,
                ...{
                    startOffset: item.StartIndex,
                    endOffset: item.EndIndex
                }
            }
            !isTextEqual(textAreaList, tempObj) && textAreaList.push(tempObj)
    }
    return item
  })
  temp.SectionList = temp.SectionList.filter(i=>i.IsPart===0)
  temp.textAreaList=textAreaList
  return temp
}

//由于在formatSemanticFile函数中改变了SectionList的值,在需要更新item.isSelectLabel时不能再使用这个函数,所以将更新isSelectLabel的逻辑再写一份
/**
 *比较isThisLabelArr/isHistoryLabelArr/isOtherLabelArr与selectArr是否有重合,若有一个重合,则isSelectLabel为true
 * @param {Object} file 
 * @param {Array} selectArr 
 */
export const checkIsSelectLabelInFile = (file,selectArr)=>{
    let temp = _.cloneDeep(file)
    temp.SectionList = temp.SectionList.map(item => {
        item.isSelectLabel = checkSelectedLabel(selectArr, item.isHistoryLabelArr) ||
            checkSelectedLabel(selectArr, item.isOtherLabelArr)||checkSelectedLabel(selectArr,item.isThisLabelArr)
        return item
    })
    temp.textAreaList = temp.textAreaList.map(item => {
        item.isSelectLabel = checkSelectedLabel(selectArr, item.isHistoryLabelArr) ||
            checkSelectedLabel(selectArr, item.isOtherLabelArr)||checkSelectedLabel(selectArr,item.isThisLabelArr)
        return item
    })
    return temp
}

/**
 * 检测标签是否全都清空
 * @param {*} labels 
 * @returns Boolean
 */
export const checkAllLabelsAreEmpty = (labels) => {
    const {isThisLabelArr,isOtherLabelArr,isHistoryLabelArr} = labels
    return isThisLabelArr.length===0&&isOtherLabelArr.length===0&&isHistoryLabelArr.length===0
}

/**
 * 格式化语义分析数据,转换成接口格式
 * @param {Object} file 
 * @returns Object
 */
export const formatCompareListItem = (file) => {
    let temp = {}
    temp.SaDocId = file.DocId
    temp.SectionList = file.SectionList.map(item => {
        const {
            isThisLabelArr
        } = item
        const LabelIds = isThisLabelArr ? isThisLabelArr.map(l => {
            return l.LabelId
        }) : []
        return {
            //段落,所有的IsPart为0,Content为空,LabelIds为isThisLabelArr[].LabelId
            SaDocSectionId: item.SectionId,
            IsPart: 0,
            Content: '',
            LabelIds
        }
    })
    const formatTextArea = file.textAreaList.map(item => {
        const {
            isThisLabelArr
        } = item
        const LabelIds = isThisLabelArr ? isThisLabelArr.map(l => {
            return l.LabelId
        }) : []
        return {
            //选定文字,所有的IsPart为1,Content为innerText,LabelIds为isThisLabelArr[].LabelId
            SaDocSectionId: item.SectionId,
            IsPart: 1,
            Content: item.innerText,
            LabelIds,
            StartIndex:item.startOffset,
            EndIndex:item.endOffset
        }
    })
    temp.SectionList = temp.SectionList.concat(formatTextArea)
    return temp
}
/**
 * 获取默认的LabelList 用于内容提取展示
 * @param {Number} docNum 有多少篇文章
 * @returns arr
 */
export const getDefaultLabelList = (docNum)=>{
  let temp = {
    SaLabelId:1,
    LabelName:'暂无关键词',
    DocList:[]
  }
  temp.DocList = Array.from({length:docNum}).map((_,index)=>{
    return {
      SaDocId:index+1,
      SectionList:[]
    }
  })
  return [temp]
}

/*------*/

/* 高亮相关 */
//参考 https://developer.mozilla.org/zh-CN/docs/Web/API/CSS_Custom_Highlight_API
/**
 * 获取dom下的文本节点
 * @param {Node} dom 
 * @returns Node[]
 */
export const getTextNodes = (dom)=>{
  const treeWalker = document.createTreeWalker(dom, NodeFilter.SHOW_TEXT);
  const allTextNodes = [];
  let currentNode = treeWalker.nextNode();
  while (currentNode) {
    allTextNodes.push(currentNode);
    currentNode = treeWalker.nextNode();
  }
  return allTextNodes
}
/**
 * 获取searchText匹配的range
 * @param {Node[]} allTextNodes 
 * @param {String} searchText 
 * @returns 
 */
export const getHighlightWordRange = (allTextNodes, searchText) => {
  const str = searchText.toLowerCase()
  const ranges = allTextNodes
    .map((el) => {
      return {
        el,
        text: el.textContent.toLowerCase()
      };
    })
    .map(({
      text,
      el
    }) => {
      const indices = [];
      let startPos = 0;
      while (startPos < text.length) {
        const index = text.indexOf(str, startPos);
        if (index === -1) break;
        indices.push(index);
        startPos = index + str.length;
      }
      // 根据搜索词的位置创建选区
      return indices.map((index) => {
        const range = new Range();
        range.setStart(el, index);
        range.setEnd(el, index + str.length);
        return range;
      });
    })
    return ranges
}

/**
 * 检查是否是合法的selection和range
 * @param {Selection} selection 通过document.getSelection获取的选区对象
 * @returns Boolean
 */
export const checkSelectionAndRange = (selection)=>{
    const {type} = selection
    return !(type!=='Range'||selection.getRangeAt(0).collapsed)
}

/**
 * 获取正确的选区范围
 * @param {Selection} selection 通过document.getSelection获取的选区对象
 * @param {Range} selectRange 通过selection.getRangeAt(0)获取的选区范围
 * @returns Object
 */
//由于不支持跨段选区,需要将选区范围限定在同一段落内
export const getCurrentRange = (selection,selectRange)=>{
    let currentRange = null
    const {anchorNode} = selection
    const {startOffset,startContainer,endOffset,endContainer} = selectRange
    //判断选区起始位置 确定选区段落
    const selectDirection = anchorNode===startContainer?'start':'end'
    const currentNode = anchorNode===startContainer?startContainer:endContainer
    const currentOffset = anchorNode===startContainer?startOffset:endOffset
    //限定选区的范围 利用Range.commonAncestorContainer判断选区是否在同一段落内
    if(selectRange.commonAncestorContainer.nodeType!==3){
        //若不在同一段落内,则将选区收起到同一段落的末尾
        //重新设置选区范围
        currentRange = new Range()
        if(selectDirection==='start'){
            //从左往右选区
            currentRange.setStart(currentNode,currentOffset)
            currentRange.setEnd(currentNode,currentNode.length)
        }else{
            //从右往左选区
            currentRange.setStart(currentNode,0)
            currentRange.setEnd(currentNode,currentOffset)
        }
        console.log('current',currentRange)
        //先清除当前选区
        selection.removeAllRanges()
        //重新选区
        selection.addRange(currentRange)
        currentRange = selection.getRangeAt(0)
    }else{
        //若在同一段落内,则selectRange就是currentRange
        currentRange = selectRange 
    }

    return {currentRange,currentNode}
}

/**
 * 生成一个textAreaRange
 * @param {Number} SectionId 段落id 
 * @param {Number} startOffset 开始下标
 * @param {Number} endOffset 结束下标
 * @param {String} innerText text内容
 * @returns 
 */

export const addNewTextRange = (SectionId,startOffset,endOffset,innerText)=>{
    let textRange = {
        SectionId:SectionId,
        innerText:innerText,
        startOffset:startOffset,
        endOffset:endOffset,
        isThisLabelArr:[],
        isHistoryLabelArr:[],
        isOtherLabelArr:[],
        LabelList:[],
        ishasThisLabel:false,
    }
    return textRange
}

/**
 * 获取指定文字的选区信息
 * @param {Object} text  格式参考 addNewTextRange
 * @returns Object
 */
export const getTextRange = (text)=>{
    let rangeInfo = {}
    let range = new Range()
    //需要取p下的文本节点
    const textNode = $(`#${text.SectionId}`)[0].childNodes[0]
    const {startOffset,endOffset} = text
    range.setStart(textNode,startOffset)
    range.setEnd(textNode,endOffset)
    const {left,right,top,bottom} = range.getBoundingClientRect()
    rangeInfo = {
        ParentNode:textNode,
        BoundingXRange:[left,right],
        BoundingYRange:[top,bottom],
        rangeWidth:right-left,//方便计算用
        StartOffset:startOffset,
        EndOffset:endOffset,
        rangeOffset:endOffset - startOffset,//方便计算用
        /* ...text */
    }
    return {rangeInfo,range}
}

/**
 * 将选定文字信息下放到对应的标签数组中
 * @param {Array} textAreaInfo 选定文字信息数组
 * @param {String} arrName 数组名称
 * @returns Array
 */
export const formatDetailArr = (textAreaInfo,arrName)=>{
    let arr = []
    textAreaInfo.forEach(textArea=>{
        let tempArr = textArea[arrName].map(label=>{
            label.areaRangeInfo = textArea.areaRangeInfo
            label.rangeSectionId = textArea.SectionId
            return label
        })
        arr = arr.concat(tempArr)
    })
    return arr
}


//更新LabelList
/**
 * 具体操作如下:
 *  1.找到LabelList中,isThisLabelArr的对应项,这些项的IsThis置为1 非对应置为0
 *  2.过滤掉isThisLabelArr中,LabelList已存在的项,就是去重
 *  3.更新LabelList
 */
/**
 * 更新LabelList
 * @param {Array} LabelList 
 * @param {Array} isThisLabelArr 
 * @returns Array
 */
export const updateLabelList = (LabelList,isThisLabelArr)=>{
    const LabelListIds = LabelList.map(l=>l.LabelId)
    const newLabelList = isThisLabelArr.filter(l=> !LabelListIds.includes(l.LabelId))
    const isThisLabelArrIds = isThisLabelArr.map(l=>l.LabelId)
    LabelList = LabelList.map(l=>{
        if(isThisLabelArrIds.includes(l.LabelId)){
            l.IsThis=1
        }else{
            l.IsThis=0
        }
        return l
    })
    return LabelList.concat(newLabelList)
}