import state from './state'
import appState from '/src/state'
import { browser } from '/src/helpers/utils'
import runtime, { getFixTime, getFirsrNoteByMeasureListIndex, getBoundingBoxByNote } from './runtime'
// @ts-ignore
import {
  isSpecialMark,
  isSpeedKeyword,
  Fraction,
  SourceMeasure,
  isGradientWords,
  GRADIENT_SPEED_RESET_TAG,
  StringUtil,
} from '/osmd-extended/src'
import dayjs from 'dayjs'
const browserInfo = browser()

const getLinkId = (): string => {
  return location.hash.split('?')[0].split('/').pop() || ''
}

export const retain = (time: number) => {
  return Math.ceil(time * 1000000) / 1000000
}

export function formatBeatUnit(beatUnit: string) {
  let multiple = 4
  switch (beatUnit) {
    case '1024th':
      // bpm = bpm;
      multiple = 1024
      break
    case '512th':
      // divisionsFromNote = (noteDuration / 4) * 512;
      multiple = 512
      break
    case '256th':
      // divisionsFromNote = (noteDuration / 4) * 256;
      multiple = 256
      break
    case '128th':
      // divisionsFromNote = (noteDuration / 4) * 128;
      multiple = 128
      break
    case '64th':
      // divisionsFromNote = (noteDuration / 4) * 64;
      multiple = 64
      break
    case '32nd':
      // divisionsFromNote = (noteDuration / 4) * 32;
      multiple = 32
      break
    case '16th':
      // divisionsFromNote = (noteDuration / 4) * 16;
      multiple = 16
      break
    case 'eighth':
      // divisionsFromNote = (noteDuration / 4) * 8;
      multiple = 8
      break
    case 'quarter':
      multiple = 4
      break
    case 'half':
      // divisionsFromNote = (noteDuration / 4) * 2;
      multiple = 2
      break
    case 'whole':
      // divisionsFromNote = (noteDuration / 4);
      multiple = 1
      break
    case 'breve':
      // divisionsFromNote = (noteDuration / 4) / 2;
      multiple = 0.5
      break
    case 'long':
      // divisionsFromNote = (noteDuration / 4) / 4;
      multiple = 0.25
      break
    case 'maxima':
      // divisionsFromNote = (noteDuration / 4) / 8;
      multiple = 0.125
      break
    default:
      break
  }

  return multiple
}

export const formatLyricsEntries = (note: any) => {
  const voiceEntries = note.parentStaffEntry?.voiceEntries || []
  const lyricsEntries: string[] = []

  for (const voic of voiceEntries) {
    if (voic.lyricsEntries?.table) {
      const values: any[] = Object.values(voic.lyricsEntries.table)
      for (const lyric of values) {
        lyricsEntries.push(lyric?.value.text)
      }
    }
  }
  return lyricsEntries
}

export const getMeasureDurationDiff = (measure: any) => {
  const { realValue: SRealValue } = measure.activeTimeSignature
  const { realValue: RRealValue } = measure.duration
  return SRealValue - RRealValue
}

export type GradualChange = {
  resetXmlNoteIndex: number
  startXmlNoteIndex: number
  endXmlNoteIndex: number
  startWord: string
}

const speedInfo: { [key in string]: number } = {
  'rall.': 1.333333333,
  'poco rit.': 1.333333333,
  'rit.': 1.333333333,
  'molto rit.': 1.333333333,
  'molto rall': 1.333333333,
  lentando: 1.333333333,
  allargando: 1.333333333,
  morendo: 1.333333333,
  'accel.': 0.8,
  calando: 2,
  'poco accel.': 0.8,
}

/** 按照dorico的渐快渐慢生成对应的速度 */
export const createSpeedInfo = (gradualChange: GradualChange | undefined, speed: number) => {
  if (gradualChange && speedInfo[gradualChange.startWord?.toLocaleLowerCase()]) {
    const notenum = Math.max(gradualChange.endXmlNoteIndex, 3)
    const speeds: number[] = []
    const startSpeed = speed
    const endSpeed = speed / speedInfo[gradualChange.startWord?.toLocaleLowerCase()]

    for (let index = 0; index < notenum; index++) {
      const speed = startSpeed + ((endSpeed - startSpeed) / notenum) * (index + 1)
      speeds.push(speed)
    }
    return speeds
  }
  return
}
const tranTime = (str: string = '') => {
  let result = str
  const splits = str.split(':')
  if (splits.length === 1) {
    result = `00:${splits[0]}:00`
  } else if (splits.length === 2) {
    result = `00:${splits[0]}:${splits[1]}`
  }
  // console.log(`1970-01-01 00:${result}0`)
  return `1970-01-01 00:${result}0`
}
export const getAllNodes = (osmd: any) => {
  //console.log(9999, osmd)
  const detailId = getLinkId()
  let fixtime = browserInfo.huawei ? 0.08 : 0 //getFixTime()
  const allNotes: any[] = []
  const allNoteId: string[] = []
  const allMeasures: any[] = []
  const { baseSpeed = 100 } = state
  const formatRealKey = (realKey: number, detail: any) => {
    // 长笛的LEVEL 2-5-1条练习是泛音练习，以每小节第一个音的指法为准，高音不变变指法。
    const olnyOneIds = ['906']
    if (olnyOneIds.includes(detailId)) {
      return detail.measures[0]?.realKey || realKey
    }
    // 圆号的LEVEL 2-5条练习是泛音练习，最后四小节指法以连音线第一个小节为准
    const olnyOneIds2 = ['782', '784']
    if (olnyOneIds2.includes(detailId)) {
      const measureNumbers = [14, 16, 30, 32]
      if (measureNumbers.includes(detail.firstVerticalMeasure?.measureNumber)) {
        return allNotes[allNotes.length - 1]?.realKey || realKey
      }
    }
    // 2-6 第三小节指法按照第一个音符显示
    const filterIds = ['900', '901', '640', '641', '739', '740', '800', '801', '773', '774', '869', '872', '714', '715']
    if (filterIds.includes(detailId)) {
      if (detail.firstVerticalMeasure?.measureNumber === 3 || detail.firstVerticalMeasure?.measureNumber === 9) {
        return detail.measures[0]?.realKey || realKey
      }
    }
    return realKey
  }
  if (state.gradualTimes) {
    console.log('合奏速度', state.gradual, state.gradualTimes)
  }
  if (osmd?.cursor) {
    try {
      osmd.cursor.reset()
    } catch (error) {}
    const iterator = osmd.cursor.iterator
    let i = 0
    let si = 0
    let measures: any[] = []
    let stepSpeeds: number[] = []
    let usetime = 0
    let relaMeasureLength = 0
    /** 弱起时间 */
    let difftime = 0
    let beatUnit = 'quarter'
    let useGradualTime = 0
    let gradualSpeed
    let gradualChange: GradualChange | undefined
    let gradualChangeIndex = 0
    let measureNumberPrinted = 1
    let indexOf = 0
    let staveIndex = 0
    let staveNoteIndex = 0
    let currentRealValueTotal = 0
    let skipNextNote = false
    let multipleRestMeasures = 0
    // const useedmeasures: Set<number> = new Set()

    while (!iterator.endReached) {
      // 为获取所有节点有修改源码currentVoiceEntries仅返回可见第一行，请搜索“修改为 仅根据当前可见声部第一行跳转”可找到位置
      // 多声部仅循环一次，以第一声部为准
      // console.log('Entries', {...osmd.cursor}, osmd.cursor.cursorElement)
      const cursorImg: HTMLElement = osmd.cursor.cursorElement
      const cursorBox: any = { move: false }
      if (cursorImg) {
        cursorBox.move = true
        cursorBox.x = cursorImg.offsetLeft
        cursorBox.y = cursorImg.offsetTop
        cursorBox.w = cursorImg.offsetWidth
        cursorBox.h = cursorImg.offsetHeight
      }
      /** 多声轨合并显示，当前音符的时值取所有声轨中的最小值 */
      let minIndex = 0, elRealValue = 0
      if (state.isCombineRender) {
        for (let index = 0; index < iterator.currentVoiceEntries.length; index++) {
          const element = iterator.currentVoiceEntries[index];
          if (element.notes[0].length.realValue < elRealValue) {
            minIndex = index
          }
          elRealValue = element.notes[0].length.realValue
        }
      }
      let voiceEntries = iterator.currentVoiceEntries?.[0] ? [iterator.currentVoiceEntries?.[0]] : []
      const voiceEntries2 = iterator.currentVoiceEntries?.[1]
      if (state.isCombineRender) {
        voiceEntries = iterator.currentVoiceEntries?.[minIndex] ? [iterator.currentVoiceEntries?.[minIndex]] : []
      }
      let skipMode = false
      for (const v of voiceEntries) {
        // 始终只取第一个声部中第一个音符的时间
        let note: any = v.notes[0]
        // console.log( note.sourceMeasure.verticalMeasureList)
        if (['Piano'].includes(state.activeDetail?.code) || state.activeDetail?.musicSheetType == 'CONCERT') {
          let _notes = []
          try {
            _notes = iterator.currentVoiceEntries
              ?.map((_n: any) => _n.notes)
              .flat()
              .sort((a: any, b: any) => a.Length.realValue - b.Length.realValue)
            note = _notes[0]
          } catch (error) {}
        }
        if (note) {
          if (si === 0) {
            allMeasures.push(note.sourceMeasure)
          }
          if (si === 0 && state.isSpecialBookCategory) {
            for (const expression of note.sourceMeasure?.TempoExpressions) {
              if (expression?.InstantaneousTempo?.beatUnit) {
                // 取最后一个有效的tempo
                beatUnit = expression.InstantaneousTempo.beatUnit
              }
            }
          }
          measureNumberPrinted = note.sourceMeasure?.MeasureNumberXML
          // 判断是否是同一小节
          if (staveIndex == note.sourceMeasure?.MeasureNumberXML) {
            staveNoteIndex++
          } else {
            // staveIndex不同,重新赋值
            staveIndex = note.sourceMeasure?.MeasureNumberXML
            staveNoteIndex = 0
          }
          let measureSpeed = note.sourceMeasure.tempoInBPM
          const { metronomeNoteIndex } = iterator.currentMeasure
          if (metronomeNoteIndex !== 0 && metronomeNoteIndex > si) {
            measureSpeed = allNotes[allNotes.length - 1]?.speed || 100
          }
          const activeVerticalMeasureList = [note.sourceMeasure.verticalMeasureList?.[0]] || []
          const { realValue } = iterator.currentTimeStamp
          const { RealValue: vRealValue, Denominator: vDenominator } = formatDuration(
            iterator.currentMeasure.activeTimeSignature,
            iterator.currentMeasure.duration
          )
          let { wholeValue, numerator, denominator, realValue: noteRealValue } = note.length

          let relativeTime = usetime //realValue * 4 * (60 / measureSpeed)
          /**
           * 速度不能为0, 此处的速度应该是按照设置的速度而不是校准后的速度，否则mp3速度不对
           */

          // let beatSpeed =
          //   (state.isSpecialBookCategory
          //     ? getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator)
          //     : baseSpeed) || 1
          // #8456 光标和节拍对应不上等bug修复， 
          let beatSpeed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1;

          let speed = (state.isSpecialBookCategory ? measureSpeed : baseSpeed) || 1
          // console.log('曲谱设置的速度', { base: getTimeByBeatUnit(beatUnit, measureSpeed, iterator.currentMeasure.activeTimeSignature.Denominator), beatSpeed, speed})
          gradualChange = iterator.currentMeasure.speedInfo || gradualChange
          gradualSpeed = osmd.sheet.soundTempos?.get(note.sourceMeasure.measureListIndex) || gradualSpeed
          if (!gradualSpeed || gradualSpeed.length < 2) {
            gradualSpeed = createSpeedInfo(gradualChange, speed)
          }
          /**
           * 渐变速度逻辑如下：
           * 1. 获取渐变关键词，记录开始区间、结束区间、渐变整体结束；
           * 2. 计算区间内每个小节最小音符时长单位，整除计算比例；
           * 3. 按照比例计算下一个音符的速度；
           * 4. 合奏曲目这里需要处理一下，因为音符位置可能不太相同，理想的情况是每个分谱指定结束时间.
           */
          let gradualLength = 0
          const measureListIndex = iterator.currentMeasure.measureListIndex
          if (state.gradualTimes && Object.keys(state.gradualTimes).length > 0) {
            const withInRangeNote = state.gradual.find((item, index) => {
              const nextItem: any = state.gradual[index + 1]
              return (
                item[0].measureIndex <= measureListIndex &&
                item[1]?.measureIndex! >= measureListIndex &&
                (!nextItem || nextItem?.[0].measureIndex !== measureListIndex)
              )
            })
            if (!withInRangeNote) {
              useGradualTime = 0
            }
            const [first, last] = withInRangeNote || []
            if (first && last) {
              // 小节数量
              const continuous = last.measureIndex - first.measureIndex
              // 开始小节内
              const inTheFirstMeasure = first.closedMeasureIndex == measureListIndex && si >= first.noteInMeasureIndex
              // 结束小节内
              const inTheLastMeasure = last.closedMeasureIndex === measureListIndex && si < last.noteInMeasureIndex
              // 范围内小节
              const inFiestOrLastMeasure =
                first.closedMeasureIndex !== measureListIndex && last.closedMeasureIndex !== measureListIndex
              if (inTheFirstMeasure || inTheLastMeasure || inFiestOrLastMeasure) {
                const startTime = state.gradualTimes[first.measureIndex]
                const endTime = state.gradualTimes[last.measureIndex]
                if (startTime && endTime) {
                  const times =
                    continuous - first.leftDuration / first.allDuration + last.leftDuration / last.allDuration
                  const diff = dayjs(tranTime(endTime)).diff(dayjs(tranTime(startTime)), 'millisecond')
                  gradualLength = ((noteRealValue / vRealValue / times) * diff) / 1000
                  useGradualTime += gradualLength
                }
              }
            }
          } else if (
            gradualChange &&
            gradualSpeed &&
            (gradualChange.startXmlNoteIndex === si || gradualChangeIndex > 0)
          ) {
            const startSpeed = gradualSpeed[0] - (gradualSpeed[1] - gradualSpeed[0])
            const { resetXmlNoteIndex, endXmlNoteIndex } = gradualChange
            const noteDiff = endXmlNoteIndex
            let stepSpeed = (gradualSpeed[gradualSpeed.length - 1] - startSpeed) / noteDiff
            stepSpeed = note.DotsXml ? stepSpeed / 1.5 : stepSpeed
            if (gradualChangeIndex < noteDiff) {
              const tempSpeed = Math.ceil(speed + stepSpeed * gradualChangeIndex)
              let tmpSpeed = getTimeByBeatUnit(
                beatUnit,
                tempSpeed,
                iterator.currentMeasure.activeTimeSignature.Denominator
              )
              const maxLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
              speed += Math.ceil(stepSpeed * (gradualChangeIndex + 1))
              tmpSpeed = getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
              const minLength = (wholeValue + numerator / denominator) * vDenominator * (60 / tmpSpeed)
              gradualLength = (maxLength + minLength) / 2
            } else if (resetXmlNoteIndex > gradualChangeIndex) {
              speed = allNotes[i - 1]?.speed
            }
            beatSpeed =
            (state.isSpecialBookCategory
              ? getTimeByBeatUnit(beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
              : baseSpeed) || 1
              const isEnd = !(gradualChangeIndex < noteDiff) && !(resetXmlNoteIndex > gradualChangeIndex)
            gradualChangeIndex++
            // console.log(gradualChangeIndex)
            if (isEnd) {
              gradualChangeIndex = 0
              gradualChange = undefined
              gradualSpeed = undefined
              stepSpeeds = []
            }
          }
          if (i === 0) {
            fixtime += getFixTime(beatSpeed)
          }
          // console.log(fixtime, '看看1',beatSpeed, baseSpeed,beatUnit, speed, iterator.currentMeasure.activeTimeSignature.Denominator)
          // 酷乐秀计算音符时值方法
          // let noteLength =
          // (numerator === 0 && note.isRestFlag ? vRealValue : (wholeValue + numerator) / denominator) *
          // vDenominator *
          // (60 / beatSpeed)
          //管乐迷计算时值方法
          let noteLength = gradualLength
            ? gradualLength
            : Math.min(vRealValue, noteRealValue) * formatBeatUnit(beatUnit) * (60 / beatSpeed)
          const measureLength = vRealValue * vDenominator * (60 / beatSpeed)
          // 单独处理个别的声部
          if (['Piano'].includes(state.activeDetail?.code)) {
            const currentRealValue = iterator.currentTimeStamp.realValue - currentRealValueTotal
            noteLength =
              (currentRealValue || (numerator === 0 ? vRealValue : (wholeValue + numerator) / denominator)) *
              vDenominator *
              (60 / beatSpeed)
          }

          /**
           * 
           * bug修复说明
           * 曲目：'我和我的祖国’，
           * 从第32节点后播放异常，进和管乐迷endtime值对比，发现是noteLength计算不一致导致
           * 不一致的原因是：noteLength值计算，多了下面这段逻辑，故先注释掉
           * 20.23.10.13
           * 
           */

          // 如果是休止符并且整个小节休止,休止符的时值小于小节时值,取小节的时值
          // if (note.isRestFlag && note?.sourceMeasure?.allRests) {
          //   multipleRestMeasures = note?.sourceMeasure?.multipleRestMeasures
          // }
          // if (multipleRestMeasures > 0) {
          //   multipleRestMeasures -= 1
          //   noteLength = measureLength
          // }

          // 如果休止符的时值大于小节的时值
          if (note.isRestFlag && noteLength > measureLength) {
            noteLength = measureLength
          }
          // 处理附点时长不正确问题
          if (note.DotsXml && note.tuplet) {
            console.log('处理附点时长不正确问题')
            noteLength = noteLength * 1.5
          }
          // 后倚音通过跳过的方式实现
          if (skipNextNote) {
            noteLength = 0.0000001
            skipNextNote = false
            skipMode = true
          }
          const Expressions = note.sourceMeasure.staffLinkedExpressions?.[0]
          for (const Expression of Expressions || []) {
            if (Expression) {
              const needSkip = Expression.expressions?.find((item: any) => item.label === '跳过下一个')
              if (needSkip && Fraction.Equal(note.voiceEntry?.Timestamp, Expression.Timestamp)) {
                skipNextNote = true
                break
              }
            }
          }
          currentRealValueTotal = iterator.currentTimeStamp.realValue
          usetime += noteLength
          relaMeasureLength += noteLength
          //console.log('👀看看noteLength',noteLength,relativeTime, i)
          let relaEndtime = noteLength + relativeTime
          // console.log({noteLength,relativeTime ,relaEndtime, endtime: relaEndtime + fixtime})
          const fixedKey = note.ParentVoiceEntry.ParentVoice.Parent.SubInstruments[0].fixedKey || 0
          // const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[si]
          const svgElelent = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables[staveNoteIndex]
          // console.log(relativeTime)
          if (allNotes.length && allNotes[allNotes.length - 1].relativeTime === relativeTime) {
            continue
          }
          // console.log(iterator.currentMeasure)
          // 如果是弱起就补齐缺省的时长
          if (i === 0) {
            const diff = getMeasureDurationDiff(iterator.currentMeasure);
            let _firstMeasureRealValue = 0
            const staffEntries = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries || []
            //计算第一个小节里面的音符时值是否等于整个小节的时值
            staffEntries.forEach((_a: any) => {
              if(_a?.sourceStaffEntry?.voiceEntries?.[0]?.notes?.[0]?.length?.realValue){
                _firstMeasureRealValue += _a.sourceStaffEntry.voiceEntries[0].notes[0].length.realValue
              }
            })
            // if (_firstMeasureRealValue < vRealValue){
            //   // console.log(_firstMeasureRealValue, vRealValue)
            //   // 如果是弱起,将整个小节的时值减去音符的时值,就是缺省的时值
            //   difftime = measureLength - noteLength
            // }
            if (diff > 0) {
              difftime = diff * formatBeatUnit(beatUnit) * (60 / beatSpeed);
            }
            /** 如果是酷乐秀MIDI类型文件就不处理弱起 */
            if (state.activeDetail?.audioType === 'MIDI') {
              difftime = 0
            }
            fixtime += difftime
          }
          // console.log('👀看看endtime', relaEndtime, fixtime, i)
          const _noteLength = noteRealValue
          const nodeDetail = {
            _noteLength,
            fixtime,
            skipMode,
            NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
            cursorBox,
            skipNextNote,
            measureNumberPrinted,
            difftime,
            octaveOffset: activeVerticalMeasureList[0]?.octaveOffset,
            frequency: note.pitch?.frequency,
            speed,
            beatSpeed,
            i,
            si,
            stepSpeeds,
            indexOfMeasures: indexOf,
            measureOpenIndex: allMeasures.length - 1,
            measures, //: groupMeasures[indexOf],
            tempoInBPM: note.sourceMeasure.tempoInBPM,
            measureLength,
            relaMeasureLength,
            id: svgElelent?.attrs.id,
            note: note.halfTone + 12, // see issue #224
            relativeTime: retain(relativeTime),
            time: retain(relativeTime + fixtime),
            endtime: retain(relaEndtime + fixtime),
            relaEndtime: retain(relaEndtime),
            realValue,
            halfTone: note.halfTone,
            voiceEntry: {
              isStaccato: note.voiceEntry.isStaccato(),
              Timestamp: { ...note.voiceEntry.Timestamp },
              ornamentContainer: note.voiceEntry.ornamentContainer ? { ...note.voiceEntry.ornamentContainer } : '',
            },
            noteElement: {
			  ...note.SourceMeasure,	
              NoteToGraphicalNoteObjectId: note.NoteToGraphicalNoteObjectId,
              notehead: note.notehead
                ? {
                    filled: note.notehead.filled,
                    shape: note.notehead.shape,
                    sourceNote: note.notehead.sourceNote?.NoteToGraphicalNoteObjectId,
                  }
                : '',
              noteheadColor: note.noteheadColor,
              isRestFlag: note.isRestFlag,
              sourceMeasure: {
				...note.SourceMeasure,
                measureListIndex: note?.SourceMeasure?.measureListIndex,
                MeasureNumberXML: note?.SourceMeasure?.MeasureNumberXML,
                allRests: note?.SourceMeasure?.allRests,
                isRestFlag: note?.SourceMeasure?.isRestFlag,
                multipleRestMeasures: note?.SourceMeasure?.multipleRestMeasures,
                verticalMeasureList: Array.isArray(note?.SourceMeasure?.verticalMeasureList)
                  ? note.SourceMeasure.verticalMeasureList.map((v: any) => {
                      const { x, y, width, height, start_x, end_x } = v?.stave || {}
                      return v
                        ? {
                            stave: {
                              x,
                              y,
                              width,
                              height,
                              start_x,
                              end_x,
                            },
                            boundingBox:
                              v && v.boundingBox
                                ? {
                                    absolutePosition: { ...v.boundingBox.absolutePosition },
                                    size: { ...v.boundingBox.size },
                                  }
                                : '',
                          }
                        : undefined
                    })
                  : [],
                activeTimeSignature: note.sourceMeasure?.activeTimeSignature,
				ActiveTimeSignature: note.sourceMeasure?.activeTimeSignature
              },
              tie: note.tie
                ? {
                    StartNote: {
                      NoteToGraphicalNoteObjectId: note.tie.StartNote.NoteToGraphicalNoteObjectId,
                    },
                    notes:
                      (Array.isArray(note.tie.notes) &&
                        note.tie.notes?.map((_tie: any) => {
                          return {
                            NoteToGraphicalNoteObjectId: _tie.NoteToGraphicalNoteObjectId,
                          }
                        })) ||
                      [],
                  }
                : '',
              slurs: Array.isArray(note.slurs)
                ? note.slurs.map((slur: any) => {
                    return {
                      startNote: {
                        NoteToGraphicalNoteObjectId: slur.startNote.NoteToGraphicalNoteObjectId,
                      },
                      endNote: {
                        NoteToGraphicalNoteObjectId: slur.endNote.NoteToGraphicalNoteObjectId,
                      },
                    }
                  })
                : [],
              pitch: {
                prevFrequency: note.pitch?.prevFrequency,
                nextFrequency: note.pitch?.nextFrequency,
                frequency: note.pitch?.frequency,
              },
              Length: {
                ...note.Length,
              },
            },
            svgElelent: svgElelent || '',
            fixedKey,
            realKey: 0,
            duration: 0,
            formatLyricsEntries: formatLyricsEntries(note),
            stave:
              activeVerticalMeasureList[0] && activeVerticalMeasureList[0].stave
                ? {
                    attrs: activeVerticalMeasureList[0].stave ? { ...activeVerticalMeasureList[0].stave.attrs } : {},
                  }
                : '',
            firstVerticalMeasure: { measureNumber: activeVerticalMeasureList?.[0]?.measureNumber },
            noteLength: 1,
            halfTone1: Array.isArray(v.notes) ? v.notes.map((n: any) => n.halfTone + 12).filter(Boolean) : [],
            halfTone2:
              voiceEntries2 && Array.isArray(voiceEntries2.notes)
                ? voiceEntries2.notes.map((n: any) => n.halfTone + 12).filter(Boolean)
                : [],
          }
          nodeDetail.realKey = formatRealKey(note.halfTone - fixedKey * 12, nodeDetail)
          nodeDetail.duration = nodeDetail.endtime - nodeDetail.time
          // console.log(i,nodeDetail.duration)
          const tickables = activeVerticalMeasureList[0]?.vfVoices['1']?.tickables || []
          const sublength = note.sourceMeasure.verticalMeasureList?.[0]?.staffEntries?.length || tickables.length
          nodeDetail.noteLength = sublength || 1
          allNotes.push(nodeDetail)
          allNoteId.push(nodeDetail.id)
          measures.push(nodeDetail)
          if (si < sublength - 1) {
            si++
          } else {
            si = 0
            relaMeasureLength = 0
            measures = []
          }
        }
      }
      // iterator.moveToNextVisibleVoiceEntry(false)
      osmd.cursor.next()
      i++
    }
    try {
      osmd.cursor.reset()
    } catch (error) {}
  }
  // 按照时间轴排序
  // console.log('看看👀', allNotes)
  const sortArray = allNotes
    .sort((a, b) => a.relativeTime - b.relativeTime)
    .map((item, index) => ({ ...item, i: index }))
  // for (let i = 0; i < sortArray.length; i++) {
  //   const note = { ...sortArray[i] }
  //   const prevNote = sortArray[i - 1]
  //   const isNotNeedStop = note.noteElement.tie && prevNote?.noteElement.tie && note.halfTone === prevNote?.halfTone
  //   const isOvertone = false
  //   if (prevNote) {
  //     if (isNotNeedStop || isOvertone) {
  //       note.sourceStartTime = note.time
  //       note.sourceRelativeTime = note.relativeTime
  //       note.sourceRealValue = note.realValue
  //       note.sourceEndTime = note.endtime
  //       note.sourceRelaEndtime = note.relaEndtime
  //       note.relativeTime = prevNote.relativeTime
  //       note.realValue = prevNote.realValue
  //       note.time = prevNote.time
  //       note.endtime = prevNote.endtime
  //       note.relaEndtime = prevNote.relaEndtime
  //     }
  //     // 此处会导致休止符继续上一个音的指法
  //     if (note.halfTone === 0) {
  //       note.realKey = prevNote.realKey
  //     }
  //   }
  //   sortArray[i] = note
  // }
  // console.log(sortArray)
  return sortArray
}

export const getAllNoteElements = (osmd: any) => {
  const list: any[] = []
  const listById: {
    [key: string]: any
  } = {}

  for (const measure of osmd.drawer.graphicalMusicSheet.measureList) {
    const activeMeasure = measure[0]
    for (const tickable of activeMeasure.vfVoices['1'].tickables) {
      list.push(tickable)
      listById[tickable.attrs.id] = tickable
    }
  }

  return {
    list,
    listById,
  }
}

export const setStepIndex = (osmd: any, num: number, prev?: number) => {
  // console.log('进度',num,prev)
  if (osmd.product) {
    if (num || num === 0) {
      // console.log(prev, num)
      if (prev && num - prev === 1) {
        osmd.cursor.setPosition({ ...state.times[num].cursorBox })
      } else if (prev && num - prev > 0) {
        while (num - prev > 0) {
          prev++
          osmd.cursor.setPosition({ ...state.times[prev].cursorBox })
        }
      } else {
        osmd.cursor.setPosition({ ...state.times[num].cursorBox })
      }
    }
  } else {
    if (num || num === 0) {
      // console.log(prev, num)
      if (prev && num - prev === 1) {
        osmd.cursor.next()
      } else if (prev && num - prev > 0) {
        while (num - prev > 0) {
          prev++
          num - prev > 0
          osmd.cursor.next()
        }
      } else {
        let i = 0
        osmd.cursor.reset()
        while (i < num) {
          i++
          if (osmd.cursor.hidden !== false) {
            osmd.cursor.show()
          } else {
            // console.log(i, num)
            osmd.cursor.next()
          }
        }
      }
    }
  }
}

export const getIndex = (times: any[], currentTime: Number) => {
	// console.log(currentTime)
	if (currentTime > state.times[state.times.length - 1].endtime) {
		return state.times.length - 1;
	}
	let index = 0;
	for (let i = 0; i < times.length; i++) {
		const item = times[i];
		const prevItem = times[i - 1];
		if (currentTime >= item.time) {
			if (!prevItem || item.time != prevItem.time) {
				index = item.i;
			}
		} else {
			break;
		}
	}
	if (state.sectionStatus && state.section.length === 2) {
		// 限制不超过此范围
		const startSection = state.befireSection || state.section[0];
		index = Math.min(Math.max(index, startSection.i), state.section[1].i);
		// console.log('endIndex', index)
	}
	return index;
};

export const getSlursNote = (_note: any, pos?: 'start' | 'end') => {
  const note: any =
    state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _note.NoteToGraphicalNoteObjectId) || {}
  let itemNote = pos === 'end' ? note.noteElement.slurs[0]?.endNote : note.noteElement.slurs[0]?.startNote
  // console.log("🚀 ~ itemNote", itemNote, note)
  if (!itemNote) return undefined
  return state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == itemNote.NoteToGraphicalNoteObjectId)
}

export const getNoteBySlursStart = (note: any, anyNoteHasSlurs?: boolean, pos?: 'start' | 'end') => {
  let activeNote = note
  let slursNote = getSlursNote(activeNote, pos)
  if (!slursNote && anyNoteHasSlurs) {
    for (const _item of activeNote.measures) {
      const item = state.times.find((n: any) => n.NoteToGraphicalNoteObjectId == _item.NoteToGraphicalNoteObjectId)
      // console.log("🚀 ~ item", item)
      if (item.noteElement.slurs.length) {
        slursNote = getSlursNote(item, pos)
        activeNote = item
      }
    }
  }
  if (activeNote && slursNote !== activeNote.noteElement) {
    // time = activeNote.time
    for (const note of state.times) {
      if (slursNote === note.noteElement) {
        return note
      }
    }
  }
  return activeNote
}

/** 根据 noteElement 获取note */
export const getParentNote = (note: any) => {
  let parentNote
  if (note) {
    // time = activeNote.time
    for (const n of state.times) {
      if (note.NoteToGraphicalNoteObjectId === n.noteElement.NoteToGraphicalNoteObjectId) {
        // console.log(note)
        return n
      }
    }
  }
  return parentNote
}

/** 获取小节之间的连音线，仅同音高*/
export const getNoteByMeasuresSlursStart = (note: any) => {
  let activeNote = note
  let tieNote
  // console.log(note.noteElement)
  if (note.noteElement.tie && note.noteElement.tie.StartNote) {
    tieNote = note.noteElement.tie.StartNote
  }
  if (activeNote && tieNote && tieNote !== activeNote.noteElement) {
    // time = activeNote.time
    for (const note of state.times) {
      if (tieNote.NoteToGraphicalNoteObjectId === note.noteElement.NoteToGraphicalNoteObjectId) {
        // console.log(note)
        return note
      }
    }
  }
  return activeNote
}

export const getActtiveNoteByTimes = (evt: MouseEvent) => {
	const el = (evt.target as HTMLDivElement)?.dataset;
	// console.log(state)

	const data: any = {};
	for (const time of state.times) {
		if (time.id && !data[time.id]) {
			data[time.id] = time;
		}
	}
	const activeNote = data[el.id || ""];
	// state.timesById = data
	return activeNote;
};

const getPrevHasSourceNote = (note: any) => {
  const indexOf = Math.max(state.times.indexOf(note) - 1, 0)

  for (let index = indexOf; index >= 0; index--) {
    const item = state.times[index]
    if (item?.stave) {
      return item
    }
  }
}

export const getBoundingBoxByverticalNote = (note: any) => {
  let measures = note?.noteElement?.sourceMeasure?.verticalMeasureList
  measures =
    !measures || !measures[0]
      ? note?.noteElement?.isRestFlag && getPrevHasSourceNote(note)?.noteElement?.sourceMeasure?.verticalMeasureList
      : measures
  let height = 0
  if (measures) {
    const firstMeasure = measures[runtime.partIndex]
    for (let index = 0; index < measures.length; index++) {
      const measure = measures[index]
      if (measure?.stave) {
        const { height: measureHeight } = measure?.stave
        if (index > 0) {
          height += measures[index - 1]?.stave.height
        }
        height += measureHeight
        const { x, y, width, context, start_x, end_x } = firstMeasure?.stave
        return {
          measureIndex: note?.noteElement?.sourceMeasure.measureListIndex || 0,
          MeasureNumberXML: note?.noteElement?.sourceMeasure.MeasureNumberXML || 1,
          start_x,
          end_x,
          height,
          x,
          y,
          width,
          context,
        }
      }
    }
  }
  return {
    measureIndex: 0,
    height,
    start_x: 0,
    end_x: 0,
    x: 0,
    y: 0,
    width: 0,
    context: {
      element: null,
    },
  }
}

export const getDuration = (osmd: any): any => {
  if (osmd) {
    const firstMeasure = osmd?.graphic?.measureList[0][0]
    // console.log(osmd?.graphic?.measureList[0][0]?.parentSourceMeasure)
    if (firstMeasure) {
      const { duration, tempoInBPM, activeTimeSignature, TempoExpressions } = firstMeasure?.parentSourceMeasure
      if (duration) {
        let beatUnit = 'quarter'
        for (const item of TempoExpressions) {
          beatUnit = item.InstantaneousTempo.beatUnit || 'quarter'
        }
        return {
          ...formatDuration(activeTimeSignature, duration),
          tempoInBPM,
          beatUnit,
        }
      }
    }
  }
  return {}
}

export const formatDuration = (activeTimeSignature: Fraction, duration: Fraction): Fraction => {
  // 弱起第一小节duration不对
  return activeTimeSignature
}

/** 根据音符单位，速度，几几拍计算正确的时间 */
export const getTimeByBeatUnit = (beatUnit: string, bpm: number, denominator: number) => {
  let multiple = 4
  switch (beatUnit) {
    case '1024th':
      // bpm = bpm;
      multiple = 1024
      break
    case '512th':
      // divisionsFromNote = (noteDuration / 4) * 512;
      multiple = 512
      break
    case '256th':
      // divisionsFromNote = (noteDuration / 4) * 256;
      multiple = 256
      break
    case '128th':
      // divisionsFromNote = (noteDuration / 4) * 128;
      multiple = 128
      break
    case '64th':
      // divisionsFromNote = (noteDuration / 4) * 64;
      multiple = 64
      break
    case '32nd':
      // divisionsFromNote = (noteDuration / 4) * 32;
      multiple = 32
      break
    case '16th':
      // divisionsFromNote = (noteDuration / 4) * 16;
      multiple = 16
      break
    case 'eighth':
      // divisionsFromNote = (noteDuration / 4) * 8;
      multiple = 8
      break
    case 'quarter':
      multiple = 4
      break
    case 'half':
      // divisionsFromNote = (noteDuration / 4) * 2;
      multiple = 2
      break
    case 'whole':
      // divisionsFromNote = (noteDuration / 4);
      multiple = 1
      break
    case 'breve':
      // divisionsFromNote = (noteDuration / 4) / 2;
      multiple = 0.5
      break
    case 'long':
      // divisionsFromNote = (noteDuration / 4) / 4;
      multiple = 0.25
      break
    case 'maxima':
      // divisionsFromNote = (noteDuration / 4) / 8;
      multiple = 0.125
      break
    default:
      break
  }
  return (denominator / multiple) * bpm
}

/** 获取第一个小节的节拍速度，替换初始的速度 */
export const getFirstBpmByMeasures = (measures: SourceMeasure[]) => {
  for (const item of measures) {
    if (item.TempoInBPM || (item as any).tempoInBPM) {
      return getMeasureRealBpm(item)
    }
  }
  return 90
}

/**
 * 根据小节获取当前节拍下真实的速度
 * 如： 6/8拍 4分音符bpm速度为120 则计算出8分音符的速度为120 * 2
 */
export const getMeasureRealBpm = (measure: SourceMeasure) => {
  let bpm = measure.TempoInBPM
  let tempoExpression = null
  let activeTimeSignature: Fraction | null = null
  for (const expression of measure?.TempoExpressions) {
    if (expression?.InstantaneousTempo.beatUnit) {
      // 取最后一个有效的tempo
      tempoExpression = expression
      activeTimeSignature = measure.ActiveTimeSignature
    }
  }
  if (tempoExpression && activeTimeSignature) {
    bpm = tempoExpression?.InstantaneousTempo.TempoInBpm
    // let multiple = 1
    // console.log(tempoExpression.InstantaneousTempo.beatUnit)

    bpm = getTimeByBeatUnit(tempoExpression.InstantaneousTempo.beatUnit, bpm, activeTimeSignature.Denominator)
  }
  return bpm
}

export const getEnvHostname = () => {
  if (location.origin.indexOf('online') > -1) {
    return 'https://mstuonline.dayaedu.com'
  } else if (location.origin.indexOf('dev') > -1) {
    return 'http://mstudev.dayaedu.com'
  }
  return 'https://mstutest.dayaedu.com'
}

export const getTvIconUrl = () => {
  if (location.origin.indexOf('online') > -1) {
    return 'https://mteaonline.dayaedu.com/#/guide'
  } else if (location.origin.indexOf('dev') > -1) {
    return 'http://mteadev.dayaedu.com/#/guide'
  }
  return 'https://mteatest.dayaedu.com/#/guide'
}

export const setPrefix = (url: string): string => {
  if (url) {
    return '?' + url
  }
  return ''
}

export type InitXmlInfo = {
  title?: string
}

export const formatXML = (xml: string, initInfo?: InitXmlInfo): string => {
  if (!xml) return ''
  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  const measures = xmlParse.getElementsByTagName('measure')
  let beats = -1
  let beatType = -1

  /** 创建默认速度标记，避免无速度导致问题 */
  // const defaultSpeedTag = document.createElement('direction')

  // const defaultSpeedTagString = `<direction-type>
  //  <metronome default-y="30" color="#000000" font-family="Opus Text Std" font-style="normal" font-size="2.0153" font-weight="normal">
  //   <beat-unit>quarter</beat-unit>
  //   <per-minute>100</per-minute>
  //  </metronome>
  // </direction-type>
  // <voice>1</voice>
  // <staff>1</staff>`

  // defaultSpeedTag.innerHTML = defaultSpeedTagString
  // if (xmlParse.getElementsByTagName('per-minute').length === 0) {
  //   measures[0]?.insertAdjacentElement('afterbegin', defaultSpeedTag)
  // }
  // 小节中如果没有节点默认为休止符
  for (const measure of measures) {
    if (beats === -1 && measure.getElementsByTagName('beats').length) {
      beats = parseInt(measure.getElementsByTagName('beats')[0].textContent || '4')
    }
    if (beatType === -1 && measure.getElementsByTagName('beat-type').length) {
      beatType = parseInt(measure.getElementsByTagName('beat-type')[0].textContent || '4')
    }
    // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
    //   speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
    // }
    const divisions = parseInt(measure.getElementsByTagName('divisions')[0]?.textContent || '256')
    if (measure.getElementsByTagName('note').length === 0) {
      const forwardTimeElement = measure.getElementsByTagName('forward')[0]?.getElementsByTagName('duration')[0]
      if (forwardTimeElement) {
        forwardTimeElement.textContent = '0'
      }
      measure.innerHTML =
        measure.innerHTML +
        `
        <note>
          <rest measure="yes"/>
          <duration>${divisions * beats}</duration>
          <voice>1</voice>
          <type>whole</type>
        </note>`
    }
  }
  if (initInfo) {
    const workTitle = xmlParse.querySelector('work-title')
    if (workTitle && initInfo.title) {
      workTitle.textContent = initInfo.title
    }
  }
  return new XMLSerializer().serializeToString(xmlParse)
}

export type CustomInfo = {
  showSpeed: boolean
  parsedXML: string
  code: string
}

/** 从xml中获取自定义信息，并删除多余的字符串 */
export const getCustomInfo = (xml: string): CustomInfo => {
  const data = {
    showSpeed: true,
    parsedXML: xml,
    code: '',
  }
  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')

  const words = xmlParse.getElementsByTagName('words')
  for (const word of words) {
    if (word && word.textContent?.trim() === '隐藏速度') {
      data.showSpeed = false
      word.textContent = ''
    }
    if (word && word.textContent?.trim() === '@') {
      word.textContent = 'segno'
    }
  }
  data.parsedXML = new XMLSerializer().serializeToString(xmlParse)
  data.code = xmlParse.querySelector('part-name')?.innerHTML || ''
  return data
}

/**
 * 替换文本标签中的内容
 */
const replaceTextConent = (beforeText: string, afterText: string, ele: Element): Element => {
  const words = ele?.getElementsByTagName('words')
  for (const word of words) {
    if (word && word.textContent?.trim() === beforeText) {
      word.textContent = afterText
    }
  }
  return ele
}

/**
 * 添加第一分谱信息至当前分谱
 * @param ele 需要插入的元素
 * @param fitstParent 合奏谱第一个分谱
 * @param parent 需要添加的分谱
 */
const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
  let noteIndex: number = 0
  if (!fitstParent) {
    return
  }
  for (let index = 0; index < fitstParent.childNodes.length; index++) {
    const element = fitstParent.childNodes[index]
    if (element.nodeName === 'note') {
      noteIndex++
    }
    if (element === ele) {
      break
    }
  }
  if (noteIndex === 0 && parent) {
    parent.insertBefore(ele, parent.childNodes[0])
    return
  }
  if (parent && parent.childNodes.length > 0) {
    let noteIndex2: number = 0
    for (let index = 0; index < parent.childNodes.length; index++) {
      const element = parent.childNodes[index]
      if (element.nodeName === 'note') {
        noteIndex2 = noteIndex2 + 1
        if (noteIndex2 === noteIndex) {
          parent.insertBefore(ele, element)
          break
        }
      }
    }
  }
  // console.log(noteIndex, parent)
}

/**
 * 检查传入文字是否为重复关键词
 * @param text 总谱xml
 * @returns 是否是重复关键词
 */
export const isRepeatWord = (text: string): boolean => {
  if (text) {
    const innerText = text.toLocaleLowerCase()
    const dsRegEx: string = 'd\\s?\\.s\\.'
    const dcRegEx: string = 'd\\.\\s?c\\.'

    return (
      innerText === '@' ||
      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al fine', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + ' al coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al fine', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + ' al coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'da\\s?capo', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'dal\\s?segno', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'al\\s?coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'to\\s?coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'a (la )?coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'fine', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'coda', true) ||
      StringUtil.StringContainsSeparatedWord(innerText, 'segno', true)
    )
  }
  return false
}

export const onlyVisible = (xml: string, partIndex: number): string => {
  if (!xml) return ''
  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  const partList = xmlParse.getElementsByTagName('part-list')?.[0]?.getElementsByTagName('score-part') || []
  const partListNames = Array.from(partList).map(
    (item) => item.getElementsByTagName('part-name')?.[0].textContent || ''
  )
  const parts = xmlParse.getElementsByTagName('part') || []
  if (!parts.length) {
    // throw new Error('')
    return ''
  }
  // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
  const firstMeasures = [...parts[0]?.getElementsByTagName('measure')]
  const metronomes = [...parts[0]?.getElementsByTagName('metronome')]
  const words = [...parts[0]?.getElementsByTagName('words')]
  const rehearsals = [...parts[0]?.getElementsByTagName('rehearsal')]

  /** 第一分谱如果是约定的配置分谱则跳过 */
	if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
		partIndex++;
		partListNames.shift();
	}
  const visiblePartInfo = partList[partIndex]
  state.partListNames = partListNames
  if (visiblePartInfo) {
    const id = visiblePartInfo.getAttribute('id')
    Array.from(parts).forEach((part) => {
      if (part && part.getAttribute('id') !== id) {
        part.parentNode?.removeChild(part)
        // 不等于第一行才添加避免重复添加
      } else if (part && part.getAttribute('id') !== 'P1') {
        // 速度标记仅保留最后一个
        const metronomeData: {
          [key in string]: Element
        } = {}
        for (let i = 0; i < metronomes.length; i++) {
          const metronome = metronomes[i]
          const metronomeContainer = metronome.parentElement?.parentElement?.parentElement
          if (metronomeContainer) {
            const index = firstMeasures.indexOf(metronomeContainer)
            metronomeData[index] = metronome
          }
        }
        Object.values(metronomeData).forEach((metronome) => {
          const metronomeContainer = metronome.parentElement?.parentElement
          const parentMeasure = metronomeContainer?.parentElement
          const measureMetronomes = [...(parentMeasure?.childNodes || [])]
          const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1
          // console.log(parentMeasure)
          if (parentMeasure && metronomesIndex > -1) {
            const index = firstMeasures.indexOf(parentMeasure)
            const activeMeasure = part.getElementsByTagName('measure')[index]
            setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure)
            //   console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
            //   activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
            //   // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
            //   // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
          }
        })
        /** word比较特殊需要精确到note位置 */
        words.forEach((word) => {
          const text = word.textContent || ''
          if (
            (isSpecialMark(text) ||
              isSpeedKeyword(text) ||
              isGradientWords(text) ||
              isRepeatWord(text) ||
              GRADIENT_SPEED_RESET_TAG) &&
            text
          ) {
            const wordContainer = word.parentElement?.parentElement
            const parentMeasure = wordContainer?.parentElement
            const measureWords = [...(parentMeasure?.childNodes || [])]
            const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1
            if (wordContainer && parentMeasure && wordIndex > -1) {
              const index = firstMeasures.indexOf(parentMeasure)
              const activeMeasure = part.getElementsByTagName('measure')[index]
              setElementNoteBefore(wordContainer, parentMeasure, activeMeasure)
              // activeMeasure?.insertBefore(wordContainer.cloneNode(true), activeMeasure?.childNodes[wordIndex])
            }
          }
        })
        rehearsals.forEach((rehearsal) => {
          const container = rehearsal.parentElement?.parentElement
          const parentMeasure = container?.parentElement
          // console.log(rehearsal)
          if (parentMeasure) {
            const index = firstMeasures.indexOf(parentMeasure)
            part.getElementsByTagName('measure')[index]?.appendChild(container.cloneNode(true))
            // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
          }
        })
      }

      // 最后一个小节的结束线元素不在最后 调整
      if (part && part.getAttribute('id') === id) {
        const barlines = part.getElementsByTagName('barline')
        const lastParent = barlines[barlines.length - 1]?.parentElement
        if (lastParent?.lastElementChild?.tagName !== 'barline') {
          const children = lastParent?.children || []
          for (let el of children) {
            if (el.tagName === 'barline') {
              // 将结束线元素放到最后
              lastParent?.appendChild(el)
              break
            }
          }
        }
      }
    })
    Array.from(partList).forEach((part) => {
      if (part && part.getAttribute('id') !== id) {
        part.parentNode?.removeChild(part)
      }
    })
    // 处理装饰音问题
    const notes = xmlParse.getElementsByTagName('note')
    const getNextvNoteDuration = (i: number) => {
      let nextNote = notes[i + 1]
      // 可能存在多个装饰音问题，取下一个非装饰音时值
      for (let index = i; index < notes.length; index++) {
        const note = notes[index]
        if (!note.getElementsByTagName('grace')?.length) {
          nextNote = note
          break
        }
      }
      const nextNoteDuration = nextNote?.getElementsByTagName('duration')[0]
      return nextNoteDuration
    }
    Array.from(notes).forEach((note, i) => {
      const graces = note.getElementsByTagName('grace')
      if (graces && graces.length) {
        // if (i !== 0) {
        note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
        // }
      }
    })
  }
  // console.log(new XMLSerializer().serializeToString(xmlParse))
  return new XMLSerializer().serializeToString(xmlParse)
}

// 倚音后连音线
export const appoggianceFormate = (xml: string): string => {
  if (!xml) return xml
  const xmlParse = new DOMParser().parseFromString(xml, 'text/xml')
  const graces = xmlParse.querySelectorAll('grace')
  if (!graces.length) return xml
  const getNextElement = (el: HTMLElement): HTMLElement => {
    if (el.querySelector('grace')) {
      return getNextElement(el?.nextElementSibling as HTMLElement)
    }
    return el
  }
  for (let grace of graces) {
    const notations = grace.parentElement?.querySelector('notations')
    if (notations && notations.querySelectorAll('slur').length > 1) {
      let nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement)
      if (nextEle && nextEle.querySelectorAll('slur').length > 0) {
        const slurNumber = Array.from(nextEle.querySelector('notations')?.children || []).map((el: Element) => {
          return el.getAttribute('number')
        })
        const slurs = notations.querySelectorAll('slur')
        for (let nota of slurs) {
          if (!slurNumber.includes(nota.getAttribute('number'))) {
            nextEle.querySelector('notations')?.appendChild(nota)
          }
        }
      }
    }
  }
  return new XMLSerializer().serializeToString(xmlParse)
}

export const getVoicePartInfo = () => {
  const { MusicalInstrumentClassification, chinesePartName } = appState

  let subjectId = -1
  const { partListNames, partIndex } = state
  const filterPartNames = partListNames.filter((item) => (item || '').trim() !== '')
  if (filterPartNames.length) {
    for (const Classification of Object.entries(MusicalInstrumentClassification)) {
      const [key, value] = Classification as [string, string[]]
      const activePart = partListNames[partIndex]
      // console.log({activePart, value, partListNames})
      const filterValue = value.filter((item) => item && activePart.indexOf(item || '') > -1)
      if (activePart && (filterValue.length || value.includes(activePart))) {
        if (!isNaN(+key)) {
          subjectId = +key
        }
        return {
          realPartListNames: partListNames,
          subjectId: subjectId,
          partListNames: value,
          partName: activePart,
          chinesePartName: chinesePartName[activePart] || activePart,
        }
      }
    }
  }
  return {
    subjectId: subjectId,
    partListNames: [],
  }
}

/** 根据ID获取最顶级ID */
export const isWithinScope = (tree: any[], id: number): number => {
  if (!tree) return 0
  let result = 0
  for (const item of tree) {
    if (item.id === id) {
      result = item.id
      break
    }
    if (item.sysMusicScoreCategoriesList) {
      result = isWithinScope(item.sysMusicScoreCategoriesList, id)
      if (result > 0) {
        result = item.id
      }
      if (result) break
    }
  }
  return result
}

/**
 * 特殊教材分类id
 */
export const classids = [1, 30]

/**
 * 指定id是否在给定的类别中
 * @param tree
 * @param id
 * @param parentInTree default: false
 */
export const idIsInClassIds = (tree: any[], id: number, parentInTree = false): boolean => {
  if (!tree) return false
  let result = false
  for (const item of tree) {
    // console.log(id, item.id, (parentInTree || classids.includes(item.id)))
    if (item.id === id && (parentInTree || classids.includes(item.id))) {
      result = true
      break
    }
    if (item.sysMusicScoreCategoriesList) {
      result = idIsInClassIds(item.sysMusicScoreCategoriesList, id, parentInTree || classids.includes(item.id))
      if (result) break
    }
  }
  // console.log('result', result)
  return result
}

/**
 * 获取图片地址
 * @param src 图片地址
 * @returns
 */
const getImageSize = (src: string): Promise<HTMLImageElement> => {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.src = src
    img.onload = () => {
      resolve(img)
    }
    img.onerror = (err) => reject(err)
  })
}


// 计算选择范围内的小节宽度高度
export const setSettionBackground = () => {
  // console.log(1111)
  state.sectionBoundingBoxs = []
  const [start, end] = state.section.sort((a, b) => a.i - b.i)
  let startIndex = 0,
    endIndex = 0
  const selectNoteNum = Math.abs(end.i - start.i) + 1
  startIndex = 0
  endIndex = end.noteLength
  const seteds: number[] = []
  const heights: number[] = []
  for (let index = 0; index < selectNoteNum; index++) {
    const activeIndex = index + start.i
    const activeTimeNote = state.times[activeIndex]
    const activeNote = activeTimeNote.noteElement
    const measureListIndex = activeNote.sourceMeasure.measureListIndex

    // 如果没有节拍器,默认提前一个小节
    if (index === 0 && measureListIndex !== 0 && !state.needTick) {
      const firstNote = getFirsrNoteByMeasureListIndex(measureListIndex - 1)
      const fnote = firstNote?.noteElement
      if (fnote) {
        // console.log(fnote.sourceMeasure?.measureListIndex, start.noteElement?.sourceMeasure?.measureListIndex)
        for (
          let j = fnote.sourceMeasure?.measureListIndex;
          j < start.noteElement?.sourceMeasure?.measureListIndex;
          j++
        ) {
          if (!seteds.includes(j)) {
            for (const item of state.times) {
              if (item.noteElement?.sourceMeasure?.measureListIndex === j && !seteds.includes(j)) {
                const boundingBox = getBoundingBoxByNote(item.noteElement, {
                  before: true,
                })
                state.befireSection = !boundingBox && state.befireSection ? state.befireSection : item
                if (!boundingBox) {
                  continue
                }
                state.sectionBoundingBoxs.push(boundingBox)
                heights.push(boundingBox.height)
                seteds.push(j)
              }
            }
          }
        }
      }
    }

    if (!seteds.includes(measureListIndex)) {
      seteds.push(measureListIndex)
      const boundingBox = getBoundingBoxByNote(activeNote)
      if (boundingBox) {
        state.sectionBoundingBoxs.push(boundingBox)
        heights.push(boundingBox.height)
      }
    }
  }
  state.sectionBoundingBoxs.map((item) => {
    item.height = Math.max(...heights)
    return item
  })
}