import { closeCompletion } from "@codemirror/autocomplete"
import {
  Compartment,
  EditorState,
  Extension,
  StateEffect,
} from "@codemirror/state"
import {
  EditorView,
  placeholder as placeholderExtension,
} from "@codemirror/view"
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useBasicSetup } from "@/components/CodeEditor/CodeMirror/extensions"
import {
  CODE_LANG,
  CODE_TYPE,
} from "@/components/CodeEditor/CodeMirror/extensions/interface"
import { IMPAKTAPPSCodeMirrorProps } from "@/components/CodeEditor/CodeMirror/interface"
import { applyEditorWrapperStyle } from "@/components/CodeEditor/CodeMirror/style"
import { IMPAKTAPPSCodeMirrorTheme } from "@/components/CodeEditor/CodeMirror/theme"
import { HintToolTip } from "@/components/CodeEditor/HintToolTip"
import { VALIDATION_TYPES } from "@/utils/validationFactory"
import { useTheme } from "@mui/material"
//import React = require("react")

// thk ReactCodeMirror:https://github.com/uiwjs/react-codemirror
export const IMPAKTAPPSCodeMirrorCore: FC<IMPAKTAPPSCodeMirrorProps> = (props) => {
  const {
    className,
    extensions = [],
    value,
    height = "",
    maxHeight = "",
    minHeight = "",
    width = "",
    maxWidth = "",
    minWidth = "",
    editable = true,
    readOnly = false,
    placeholder,
    showLineNumbers = false,
    lang = CODE_LANG.JAVASCRIPT,
    codeType = CODE_TYPE.EXPRESSION,
    expressions = [],
    result = "",
    hasError = false,
    resultType = VALIDATION_TYPES.STRING,
    canShowCompleteInfo = false,
    sqlScheme = {},
    singleLine,
    onChange,
    onBlur,
    onFocus,
    tooltipContainer,
    scopeOfAutoComplete,
  } = props

  const [isFocus, setIsFocus] = useState(false)

  const editorViewRef = useRef<EditorView>()
  const editorWrapperRef = useRef<HTMLDivElement | null>(null)
  const compartmentsRef = useRef<Compartment[]>([])

  const extensionOptions = useMemo(() => {
    return {
      showLineNumbers,
      lang,
      codeType,
      expressions,
      canShowCompleteInfo,
      sqlScheme,
      scopeOfAutoComplete,
    }
  }, [
    canShowCompleteInfo,
    codeType,
    expressions,
    lang,
    showLineNumbers,
    sqlScheme,
    scopeOfAutoComplete,
  ])

  const basicExtensions = useBasicSetup(extensionOptions)
const theme = useTheme()
  const defaultThemeOption = useMemo(
    () =>
      EditorView.theme({
        "&": {
          height,
          minHeight,
          maxHeight,
          width,
          maxWidth,
          minWidth,
        },
        ...IMPAKTAPPSCodeMirrorTheme(theme),
      }),
    [height, maxHeight, maxWidth, minHeight, minWidth, width],
  )

  const focusUpdateListener: Extension = useMemo(() => {
    return EditorView.updateListener.of((viewUpdate) => {
      if (viewUpdate.focusChanged) {
        setIsFocus(viewUpdate.view.hasFocus)
        const currentString = viewUpdate.state.doc.toString()

        if (viewUpdate.view.hasFocus) {
          onFocus?.()
        } else {
          onBlur?.(currentString)
        }
        if (!viewUpdate.view.hasFocus) {
          setTimeout(() => {
            closeCompletion(viewUpdate.view)
          }, 500)
        }
      }
    })
  }, [onBlur, onFocus])

  const changeUpdateListener: Extension = useMemo(() => {
    return EditorView.updateListener.of((viewUpdate) => {
      const currentString = viewUpdate.state.doc.toString()
      if (viewUpdate.docChanged) {
        onChange?.(currentString)
      }
    })
  }, [onChange])

  const readOnlyStateChangeEffect: Extension = useMemo(
    () => EditorState.readOnly.of(readOnly),
    [readOnly],
  )

  const editableStateChangeEffect: Extension = useMemo(
    () => EditorView.editable.of(editable),
    [editable],
  )

  const placeholderExt: Extension = useMemo(() => {
    return typeof placeholder === "string"
      ? placeholderExtension(placeholder)
      : []
  }, [placeholder])

  const singleLineExt: Extension = useMemo(() => {
    return singleLine
      ? EditorState.transactionFilter.of((tr) => {
          return tr.newDoc.lines > 1 ? [] : [tr]
        })
      : EditorView.lineWrapping
  }, [singleLine])

  const allExtensions = useMemo(() => {
    return [
      basicExtensions,
      defaultThemeOption,
      focusUpdateListener,
      changeUpdateListener,
      readOnlyStateChangeEffect,
      editableStateChangeEffect,
      placeholderExt,
      singleLineExt,
      extensions,
    ]
  }, [
    basicExtensions,
    defaultThemeOption,
    focusUpdateListener,
    changeUpdateListener,
    readOnlyStateChangeEffect,
    editableStateChangeEffect,
    placeholderExt,
    singleLineExt,
    extensions,
  ])

  const extensionsWithCompartment = useMemo(() => {
    for (
      let i = compartmentsRef.current.length;
      i < allExtensions.length;
      i++
    ) {
      const compartment = new Compartment()
      compartmentsRef.current.push(compartment)
    }
    return allExtensions.map((ext, index) =>
      compartmentsRef.current[index].of(ext),
    )
  }, [allExtensions])

  useEffect(() => {
    if (
      !editorViewRef.current ||
      (!isFocus && value !== editorViewRef.current.state.doc.toString())
    ) {
      const state = EditorState.create({
        doc: value,
        extensions: extensionsWithCompartment,
      })
      if (editorViewRef.current) {
        editorViewRef.current.setState(state)
      } else {
        if (editorWrapperRef.current) {
          editorViewRef.current = new EditorView({
            state,
            parent: editorWrapperRef.current,
          })
        }
      }
    }
  }, [value, extensionsWithCompartment, isFocus])

  const reconfigure = useCallback(
    (view?: EditorView) => {
      if (view) {
        const effects: StateEffect<unknown>[] = []
        allExtensions.forEach((e, i) => {
          if (compartmentsRef.current[i].get(view.state) !== e) {
            effects.push(compartmentsRef.current[i].reconfigure(e))
          }
        })
        if (effects.length > 0) {
          view.dispatch({ effects })
        }
      }
    },
    [allExtensions],
  )

  useEffect(() => {
    if (editorViewRef.current) {
      reconfigure(editorViewRef.current)
    }
  }, [reconfigure])

  return (
    <HintToolTip
      isEditorFocused={isFocus}
      result={!result ? '""' : result}
      hasError={hasError}
      resultType={resultType}
      toolTipContainer={tooltipContainer}
    >
      <div
        ref={editorWrapperRef}
        className={className}
        css={applyEditorWrapperStyle(hasError, isFocus, editable, readOnly)}
      />
    </HintToolTip>
  )
}
