import { onlyVisible } from './helpers'
import { OpenSheetMusicDisplay, SourceMeasure } from '/osmd-extended/src'

export const noteDuration = {
  '1/2': 2,
  w: 1,
  h: 0.5,
  q: 0.25,
  '8': 0.125,
  '16': 0.0625,
  '32': 0.03125,
  '64': 0.015625,
  '128': 0.0078125,
}

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

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

/**
 * 计算渐变速度
 */
export const calcGradual = () => {}

/**
 * 2022年9月14日版本 计算渐变速度，此方法不兼容之前的选择范围。
 */
export const calcGradual2 = () => {}

/**
 * 获取指定元素下一个Note元素
 * @param ele 指定元素
 * @param selectors 选择器
 */
const getNextNote = (ele: Element, selectors: string) => {
  let index = 0
  const parentEle = ele.closest(selectors)
  let pointer = parentEle
  const measure = parentEle?.closest('measure')
  let siblingNote: Element | null | undefined = null
  // 查找到相邻的第一个note元素
  while (!siblingNote && index < (measure?.childNodes.length || 50)) {
    index++
    if (pointer?.nextElementSibling?.tagName === 'note') {
      siblingNote = pointer?.nextElementSibling
    }
    pointer = pointer?.nextElementSibling!
  }
  return siblingNote
}

export type GradualElement = {
  ele: Element
  index: number
  noteInMeasureIndex: number
  textContent: string
  measureIndex: number
  type: 'words' | 'metronome'
  allDuration: number
  leftDuration: number
}

export type GradualNote = GradualItem[]

export type GradualItem = {
  start: number
  measureIndex: number
  noteInMeasureIndex: number
  allDuration: number
  leftDuration: number
  type: string
  closedMeasureIndex: number
}

// export type GradualItem = {
//   start: number
//   startMeasureIndex: number
//   startNoteInMeasureIndex: number
//   allDuration: number
//   leftDuration: number
//   endNoteInMeasureIndex?: number
//   endMeasureIndex?: number
//   end?: number
// }

/**
 * 按照xml进行减慢速度的计算
 * @param xml 始终按照第一分谱进行减慢速度的计算
 */
export const getGradualLengthByXml = (xml: string) => {
  const firstPartXml = onlyVisible(xml, 0)
  const xmlParse = new DOMParser().parseFromString(firstPartXml, 'text/xml')
  const measures = Array.from(xmlParse.querySelectorAll('measure'))
  const notes = Array.from(xmlParse.querySelectorAll('note'))
  const words = Array.from(xmlParse.querySelectorAll('words'))
  const metronomes = Array.from(xmlParse.querySelectorAll('metronome'))

  const eles: GradualElement[] = []

  for (const ele of [...words, ...metronomes]) {
    const note = getNextNote(ele, 'direction')
    // console.log(ele, note)
    if (note) {
      const measure = note?.closest('measure')!
      const measureNotes = Array.from(measure.querySelectorAll('note'))

      const noteInMeasureIndex = Array.from(measure.childNodes)
        .filter((item) => item.nodeName === 'note')
        .findIndex((item) => item === note)

      let allDuration = 0
      let leftDuration = 0
      for (let i = 0; i < measureNotes.length; i++) {
        const n = measureNotes[i]
        const duration = +(n.querySelector('duration')?.textContent || '0')
        allDuration += duration
        if (i < noteInMeasureIndex) {
          leftDuration = allDuration
        }
      }
      eles.push({
        ele,
        index: notes.indexOf(note!),
        noteInMeasureIndex,
        textContent: ele.textContent!,
        measureIndex: measures.indexOf(measure!),
        type: ele.tagName as GradualElement['type'],
        allDuration,
        leftDuration,
      })
    }
  }

  // 结尾处手动插入一个音符节点
  eles.push({
    ele: notes[notes.length - 1],
    index: notes.length,
    noteInMeasureIndex: 0,
    textContent: '',
    type: 'metronome',
    allDuration: 1,
    leftDuration: 1,
    measureIndex: measures.length,
  })

  const gradualNotes: GradualNote[] = []
  eles.sort((a, b) => a.index - b.index)
  const keys = Object.keys(speedInfo).map(w => w.toLocaleLowerCase())
  for (const ele of eles) {
    // 是否是同时也是关闭标签
  let isLastNoteAndNotClosed = false
    let closed = 0
    const textContent = ele.textContent?.toLocaleLowerCase().trim()
    if (ele === eles[eles.length - 1]) {
      if (gradualNotes[gradualNotes.length - 1]?.length === 1) {
        isLastNoteAndNotClosed = true
      }
    }
    const isKeyWork = keys.find(k => {
      const ks = k.split(' ')
      return textContent && ks.includes(textContent)
    })
    if (
      ele.type === "metronome" ||
      (ele.type === "words" && (textContent.startsWith("a tempo") || isKeyWork) || isLastNoteAndNotClosed)
    ) {
      const indexOf = gradualNotes.findIndex((item) => item.length === 1)
      if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {
        closed = -1
        gradualNotes[indexOf][1] = {
          start: ele.index,
          measureIndex: ele.measureIndex,
          closedMeasureIndex: ele.measureIndex,
          noteInMeasureIndex: ele.noteInMeasureIndex,
          allDuration: ele.allDuration,
          leftDuration: ele.leftDuration,
          type: textContent,
        }
      }
    }
    if (ele.type === 'words' && isKeyWork) {
      gradualNotes.push([
        {
          start: ele.index,
          measureIndex: ele.measureIndex,
          closedMeasureIndex: ele.measureIndex + closed,
          noteInMeasureIndex: ele.noteInMeasureIndex,
          allDuration: ele.allDuration,
          leftDuration: ele.leftDuration,
          type: textContent,
        },
      ])
    }
  }
  return gradualNotes
}

export const getGradualLength = (
  gradualChange: GradualChange,
  speed: number,
  osdm: OpenSheetMusicDisplay
) => {
  const {
    startMeasureListIndex,
    endMeasureListIndex,
    endXmlNoteIndex,
    startWord,
  } = gradualChange
  const measures: SourceMeasure[] = []
  for (
    let index = startMeasureListIndex;
    index <= endMeasureListIndex;
    index++
  ) {
    const measure = osdm.Sheet.SourceMeasures[index]
    measures.push(measure)
  }
  const allNoteDurations: number[] = []
  for (const measure of measures) {
    if (allNoteDurations.length >= endXmlNoteIndex) {
      break
    }
    // @ts-ignore
    measure.VerticalMeasureList[0]?.vfVoices['1']?.tickables?.forEach((item) =>
      allNoteDurations.push(
        noteDuration[item.duration as keyof typeof noteDuration]
      )
    )
  }
  const minDuration = Math.min(...allNoteDurations)
  const parts = allNoteDurations.map((item) => item / minDuration)
  const allParts = parts.reduce((total, val) => val + total, 0)
  // const startMeasure = osdm.Sheet.SourceMeasures[startMeasureListIndex]
  // const endMeasure = osdm.Sheet.SourceMeasures[endMeasureListIndex]
  let surplusSpeed = speed / speedInfo[startWord?.toLocaleLowerCase()] || 1
  const diffSpeed = speed - surplusSpeed
  let useSpeed = 0
  const speeds: number[] = parts.map((item) => {
    const s = ((diffSpeed - useSpeed) * item) / allParts
    return s
  })
  // 120 111.9 104.4 96.9
  // 8.1 7.5 7.2 6.9
  // 0.6 0.3 0.3
  const lingerSpeed: number[] = []
  for (let index = 0; index < speeds.length; index++) {
    const s = speeds[index]
    let beforeSpeed = 0
    let afterSpeed = 0
    for (let j = 0; j < index; j++) {
      beforeSpeed += speeds[j]
    }
    afterSpeed += beforeSpeed
    afterSpeed += s

    lingerSpeed.push((afterSpeed + beforeSpeed) / 2)
  }
  // console.log(lingerSpeed, speeds[0], speeds, parts, allParts)
  return lingerSpeed
}
