import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
import { Command, EditorView } from '@codemirror/view'
import {
  closeSearchPanel,
  openSearchPanel,
  searchPanelOpen,
} from '@codemirror/search'
import { sendMB } from '@/infrastructure/event-tracking'
import { toggleRanges, wrapRanges } from '../../commands/ranges'
import {
  ancestorListType,
  toggleListForRanges,
  unwrapBulletList,
  unwrapDescriptionList,
  unwrapNumberedList,
  wrapInBulletList,
  wrapInDescriptionList,
  wrapInNumberedList,
} from './lists'
import { snippet } from '@codemirror/autocomplete'
import { snippets } from './snippets'
import { minimumListDepthForSelection } from '../../utils/tree-operations/ancestors'
import { isVisual } from '../visual/visual'
import { sendSearchEvent } from '@/features/event-tracking/search-events'

export const toggleBold = toggleRanges('\\textbf')
export const toggleItalic = toggleRanges('\\textit')

// TODO: apply as a snippet?
// TODO: read URL from clipboard?
export const wrapInHref = wrapRanges('\\href{}{', '}', false, (range, view) =>
  isVisual(view) ? range : EditorSelection.cursor(range.from - 2)
)
export const toggleBulletList = toggleListForRanges('itemize')
export const toggleNumberedList = toggleListForRanges('enumerate')
export const wrapInInlineMath = wrapRanges('\\(', '\\)')
export const wrapInDisplayMath = wrapRanges('\n\\[', '\\]\n')

export const ensureEmptyLine = (
  state: EditorState,
  range: SelectionRange,
  direction: 'above' | 'below' = 'below'
) => {
  let pos = range.anchor
  let suffix = ''
  let prefix = ''

  const line = state.doc.lineAt(pos)

  if (line.text.trim().length) {
    if (direction === 'below') {
      pos = Math.min(line.to + 1, state.doc.length)
    } else {
      pos = Math.max(line.from - 1, 0)
    }
    const neighbouringLine = state.doc.lineAt(pos)

    if (neighbouringLine.length && direction === 'below') {
      suffix = '\n'
    } else if (neighbouringLine.length && direction === 'above') {
      prefix = '\n'
    }
  }
  return { pos, suffix, prefix }
}

export const insertFigure: Command = view => {
  const { state, dispatch } = view
  const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
  const template = `\n${snippets.figure}\n${suffix}`
  snippet(template)({ state, dispatch }, { label: 'Figure' }, pos, pos)
  return true
}

export const insertTable = (view: EditorView, sizeX: number, sizeY: number) => {
  const { state, dispatch } = view
  const visual = isVisual(view)
  const placeholder = visual ? '' : '#{}'
  const placeholderAtStart = visual ? '#{}' : ''
  const { pos, suffix } = ensureEmptyLine(state, state.selection.main)
  const template = `${placeholderAtStart}\n\\begin{table}
\t\\centering
\t\\begin{tabular}{${'c'.repeat(sizeX)}}
${(
  '\t\t' +
  `${placeholder} & ${placeholder}`.repeat(sizeX - 1) +
  '\\\\\n'
).repeat(sizeY)}\t\\end{tabular}
\t\\caption{Caption}
\t\\label{tab:placeholder}
\\end{table}${suffix}`
  snippet(template)({ state, dispatch }, { label: 'Table' }, pos, pos)
  return true
}

export const insertCite: Command = view => {
  const { state, dispatch } = view
  const pos = state.selection.main.anchor
  const template = snippets.cite
  snippet(template)({ state, dispatch }, { label: 'Cite' }, pos, pos)
  return true
}

export const insertRef: Command = view => {
  const { state, dispatch } = view
  const pos = state.selection.main.anchor
  const template = snippets.ref
  snippet(template)({ state, dispatch }, { label: 'Ref' }, pos, pos)
  return true
}

export const indentDecrease: Command = view => {
  if (minimumListDepthForSelection(view.state) < 2) {
    return false
  }
  switch (ancestorListType(view.state)) {
    case 'itemize':
      return unwrapBulletList(view)
    case 'enumerate':
      return unwrapNumberedList(view)
    case 'description':
      return unwrapDescriptionList(view)
    default:
      return false
  }
}

export const cursorIsAtStartOfListItem = (state: EditorState) => {
  return state.selection.ranges.every(range => {
    const line = state.doc.lineAt(range.from)
    const prefix = state.sliceDoc(line.from, range.from)
    return /\\item\s*$/.test(prefix)
  })
}

export const indentIncrease: Command = view => {
  if (minimumListDepthForSelection(view.state) < 1) {
    return false
  }
  switch (ancestorListType(view.state)) {
    case 'itemize':
      return wrapInBulletList(view)
    case 'enumerate':
      return wrapInNumberedList(view)
    case 'description':
      return wrapInDescriptionList(view)
    default:
      return false
  }
}

export const toggleSearch: Command = view => {
  if (searchPanelOpen(view.state)) {
    closeSearchPanel(view)
  } else {
    sendSearchEvent('search-open', {
      searchType: 'document',
      method: 'button',
      location: 'toolbar',
      mode: isVisual(view) ? 'visual' : 'source',
    })
    openSearchPanel(view)
  }
  return true
}

export const addComment = (location: string) => {
  sendMB('add-comment', {
    location,
  })
  window.dispatchEvent(new Event('add-new-review-comment'))
}

export const deleteSelection: Command = view => {
  if (view.state.selection.ranges.every(range => range.empty)) return false

  const transaction = view.state.changeByRange(range => {
    if (range.empty) {
      return { changes: [], range }
    }

    return {
      changes: { from: range.from, to: range.to, insert: '' },
      range: EditorSelection.cursor(range.from),
    }
  })

  view.dispatch(transaction)
  return true
}
