import { Annotation, Compartment } from '@codemirror/state'
import { EditorView, ViewPlugin } from '@codemirror/view'
import { themeOptionsChange } from './theme'
import { sourceOnly } from './visual/visual'
import { round } from 'lodash'
import { hasLanguageLoadedEffect } from './language'
import { fontLoad, hasFontLoadedEffect } from './font-load'

const themeConf = new Compartment()
const changeHalfLeadingAnnotation = Annotation.define<boolean>()

function firstVisibleNonSpacePos(view: EditorView) {
  for (const range of view.visibleRanges) {
    const match = /\S/.exec(view.state.sliceDoc(range.from, range.to))
    if (match) {
      return range.from + match.index
    }
  }
  return null
}

function measureHalfLeading(view: EditorView) {
  const pos = firstVisibleNonSpacePos(view)
  if (pos === null) {
    return 0
  }

  const coords = view.coordsAtPos(pos)
  if (!coords) {
    return 0
  }
  const inlineBoxHeight = coords.bottom - coords.top

  // Rounding prevents gaps appearing in some situations
  return round((view.defaultLineHeight - inlineBoxHeight) / 2, 2)
}

function createTheme(halfLeading: number) {
  return EditorView.contentAttributes.of({
    style: `--half-leading: ${halfLeading}px`,
  })
}

/**
 * A custom extension which measures the height of the first non-space position and provides a CSS variable via an editor theme,
 * used for extending elements over the whole line height using padding.
 */
const plugin = ViewPlugin.define(
  view => {
    let halfLeading = 0

    const measureRequest = {
      read: () => {
        return measureHalfLeading(view)
      },

      write: (newHalfLeading: number) => {
        if (newHalfLeading !== halfLeading) {
          halfLeading = newHalfLeading
          window.setTimeout(() =>
            view.dispatch({
              effects: themeConf.reconfigure(createTheme(newHalfLeading)),
              annotations: changeHalfLeadingAnnotation.of(true),
            })
          )
        }
      },
    }

    return {
      update(update) {
        // Ignore any update triggered by this plugin
        if (
          update.transactions.some(tr =>
            tr.annotation(changeHalfLeadingAnnotation)
          )
        ) {
          return
        }
        if (
          hasFontLoadedEffect(update) ||
          (update.geometryChanged && !update.docChanged) ||
          update.transactions.some(tr => tr.annotation(themeOptionsChange)) ||
          hasLanguageLoadedEffect(update)
        ) {
          view.requestMeasure(measureRequest)
        }
      },
    }
  },
  {
    provide: () => [themeConf.of(createTheme(0))],
  }
)

export const inlineBackground = (visual: boolean) => {
  return sourceOnly(visual, [fontLoad, plugin])
}