import * as React from 'react'
import { EditorSelection } from '@codemirror/state'
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands'
import { markdown } from '@codemirror/lang-markdown'
import { EditorState } from '@codemirror/state'
import {
  EditorView,
  highlightActiveLine,
  keymap,
  lineNumbers,
  placeholder
} from '@codemirror/view'

export type MarkdownEditorHandle = {
  applyAction: (id: string) => void | Promise<void>
  searchText: (query: string) => boolean
  getView: () => EditorView | null
}

type Props = {
  mountKey: string
  value: string
  onChange: (next: string) => void
  gotoLine?: { line: number; seq: number } | null
  showLineNumbers?: boolean
  /** In Electron, `window.prompt` is unreliable; use an in-app prompt from the host. */
  requestText?: (title: string, defaultValue: string) => Promise<string | null>
  onViewReady?: (view: EditorView | null) => void
  /** 分栏：根据光标所在行在编辑器可滚动内容中的垂直比例，对齐阅读区提示条。 */
  onHybridCursorLine?: (line: number) => void
  requestImageDetails?: () => Promise<{ url: string; alt: string } | null>
  requestColor?: () => Promise<string | null>
}

function getMainSelection(view: EditorView): { from: number; to: number } {
  const r = view.state.selection.main
  return { from: r.from, to: r.to }
}

function wrap(view: EditorView, left: string, right: string): void {
  const { from, to } = getMainSelection(view)
  const sel = view.state.sliceDoc(from, to)
  view.dispatch({
    changes: { from, to, insert: left + sel + right },
    selection: EditorSelection.range(from + left.length, from + left.length + sel.length)
  })
  view.focus()
}

function insertAtCursor(view: EditorView, text: string): void {
  const { from, to } = getMainSelection(view)
  view.dispatch({
    changes: { from, to, insert: text },
    selection: EditorSelection.cursor(from + text.length)
  })
  view.focus()
}

function insertLines(view: EditorView, text: string): void {
  insertAtCursor(view, text)
}

function linePrefixOnMainLine(view: EditorView, prefix: string): void {
  const pos = view.state.selection.main.head
  const line = view.state.doc.lineAt(pos)
  const lineText = line.text
  const hashes = /^#+\s/.exec(lineText)?.[0] ?? ''
  let rest = lineText
  if (hashes) rest = lineText.slice(hashes.length)
  const newLine = prefix + rest
  view.dispatch({
    changes: { from: line.from, to: line.to, insert: newLine },
    selection: EditorSelection.cursor(line.from + newLine.length)
  })
  view.focus()
}

function escapeMarkdownLinkFragment(s: string): string {
  return s.replace(/\\/g, '\\\\').replace(/\[/g, '\\[').replace(/\]/g, '\\]')
}

function sanitizeInlineColor(c: string): string {
  const t = c.trim()
  if (/^#[0-9a-fA-F]{3}$/.test(t)) {
    const r = t[1]!
    const g = t[2]!
    const b = t[3]!
    return `#${r}${r}${g}${g}${b}${b}`
  }
  if (/^#[0-9a-fA-F]{6}$/.test(t) || /^#[0-9a-fA-F]{8}$/.test(t)) return t.toLowerCase()
  if (/^rgba?\(\s*[\d.\s%,]+\s*\)$/.test(t)) return t
  if (/^hsla?\(\s*[\d.\s%,deg]+\s*\)$/.test(t)) return t
  if (/^[\w-]{1,40}$/.test(t)) return t
  return '#333333'
}

function sanitizeLinkUrl(url: string): string {
  const t = url.trim() || 'https://'
  if (/^file:/i.test(t)) return `<${t.replace(/[<>]/g, '')}>`
  return t.replace(/\)/g, '%29')
}

type ActionDeps = {
  requestText: (title: string, def: string) => Promise<string | null>
  requestImageDetails?: () => Promise<{ url: string; alt: string } | null>
  requestColor?: () => Promise<string | null>
}

async function applyMarkdownAction(view: EditorView, id: string, deps: ActionDeps): Promise<void> {
  const { requestText, requestImageDetails, requestColor } = deps
  try {
    switch (id) {
      case 'bold':
        wrap(view, '**', '**')
        break
      case 'italic':
        wrap(view, '*', '*')
        break
      case 'strike':
        wrap(view, '~~', '~~')
        break
      case 'code':
        wrap(view, '`', '`')
        break
      case 'codeBlock':
        insertLines(view, '\n```\n\n```\n')
        break
      case 'link': {
        const url = await requestText('链接地址', 'https://')
        if (url === null) return
        const { from, to } = getMainSelection(view)
        const rawLabel = view.state.sliceDoc(from, to) || '链接文字'
        const label = escapeMarkdownLinkFragment(rawLabel)
        const insert = `[${label}](${sanitizeLinkUrl(url)})`
        view.dispatch({
          changes: { from, to, insert },
          selection: EditorSelection.cursor(from + insert.length)
        })
        view.focus()
        break
      }
      case 'image': {
        if (requestImageDetails) {
          const det = await requestImageDetails()
          if (!det?.url?.trim()) return
          const alt = (det.alt ?? '图片').trim() || '图片'
          insertAtCursor(
            view,
            `![${escapeMarkdownLinkFragment(alt)}](${sanitizeLinkUrl(det.url.trim())})`
          )
          break
        }
        const url = await requestText('图片地址', 'https://')
        if (url === null) return
        const rawAlt = await requestText('替代文字', '图片')
        if (rawAlt === null) return
        const alt = escapeMarkdownLinkFragment(rawAlt || '图片')
        insertAtCursor(view, `![${alt}](${sanitizeLinkUrl(url)})`)
        break
      }
      case 'h1':
        linePrefixOnMainLine(view, '# ')
        break
      case 'h2':
        linePrefixOnMainLine(view, '## ')
        break
      case 'h3':
        linePrefixOnMainLine(view, '### ')
        break
      case 'h4':
        linePrefixOnMainLine(view, '#### ')
        break
      case 'h5':
        linePrefixOnMainLine(view, '##### ')
        break
      case 'h6':
        linePrefixOnMainLine(view, '###### ')
        break
      case 'ul':
        linePrefixOnMainLine(view, '- ')
        break
      case 'ol':
        linePrefixOnMainLine(view, '1. ')
        break
      case 'task':
        linePrefixOnMainLine(view, '- [ ] ')
        break
      case 'quote':
        linePrefixOnMainLine(view, '> ')
        break
      case 'hr':
        insertLines(view, '\n---\n')
        break
      case 'table':
        insertLines(
          view,
          '\n| 列1 | 列2 | 列3 |\n| --- | --- | --- |\n|  |  |  |\n'
        )
        break
      case 'mark':
        wrap(view, '<mark>', '</mark>')
        break
      case 'color': {
        if (requestColor) {
          const c = await requestColor()
          if (c === null || !c.trim()) return
          const color = sanitizeInlineColor(c)
          wrap(view, `<span style="color:${color}">`, '</span>')
          break
        }
        const c = await requestText('颜色（如 #e11 或 red）', '#dc2626')
        if (c === null || !c.trim()) return
        const color = sanitizeInlineColor(c)
        wrap(view, `<span style="color:${color}">`, '</span>')
        break
      }
      case 'sup':
        wrap(view, '<sup>', '</sup>')
        break
      case 'sub':
        wrap(view, '<sub>', '</sub>')
        break
      case 'kbd':
        wrap(view, '<kbd>', '</kbd>')
        break
      case 'details':
        insertLines(view, '\n<details>\n<summary>标题</summary>\n内容\n</details>\n')
        break
      case 'footnote':
        insertLines(view, '[^1]\n\n[^1]: 脚注内容\n')
        break
      default:
        break
    }
  } catch (e) {
    console.error('[MyReader] toolbar action failed', id, e)
  }
}

export const EditorPane = React.forwardRef<MarkdownEditorHandle, Props>(
  function EditorPane(
    {
      mountKey,
      value,
      onChange,
      gotoLine,
      showLineNumbers = true,
      requestText,
      onViewReady,
      onHybridCursorLine,
      requestImageDetails,
      requestColor
    }: Props,
    ref
  ): React.ReactElement {
    const hostRef = React.useRef<HTMLDivElement | null>(null)
    const viewRef = React.useRef<EditorView | null>(null)
    const onChangeRef = React.useRef(onChange)
    onChangeRef.current = onChange

    const requestTextRef = React.useRef(requestText)
    requestTextRef.current = requestText

    const onViewReadyRef = React.useRef(onViewReady)
    onViewReadyRef.current = onViewReady

    const onHybridCursorLineRef = React.useRef(onHybridCursorLine)
    onHybridCursorLineRef.current = onHybridCursorLine

    const requestImageDetailsRef = React.useRef(requestImageDetails)
    requestImageDetailsRef.current = requestImageDetails

    const requestColorRef = React.useRef(requestColor)
    requestColorRef.current = requestColor

    React.useImperativeHandle(
      ref,
      () => ({
        applyAction: async (id: string) => {
          const view = viewRef.current
          if (!view) return
          const rt =
            requestTextRef.current ??
            ((title: string, def: string) =>
              Promise.resolve<string | null>(window.prompt(title, def)))
          await applyMarkdownAction(view, id, {
            requestText: rt,
            requestImageDetails: requestImageDetailsRef.current,
            requestColor: requestColorRef.current
          })
        },
        searchText: (query: string): boolean => {
          const view = viewRef.current
          if (!view || !query.trim()) return false
          const doc = view.state.doc.toString()
          const idx = doc.toLowerCase().indexOf(query.toLowerCase())
          if (idx >= 0) {
            view.dispatch({
              selection: { anchor: idx, head: idx + query.length },
              scrollIntoView: true
            })
            return true
          }
          return false
        },
        getView: (): EditorView | null => viewRef.current
      }),
      []
    )

    React.useEffect(() => {
      const host = hostRef.current
      if (!host) return
      host.replaceChildren()

      const view = new EditorView({
        parent: host,
        state: EditorState.create({
          doc: value,
          extensions: [
            history(),
            markdown(),
            ...(showLineNumbers ? [lineNumbers()] : []),
            highlightActiveLine(),
            keymap.of([...defaultKeymap, ...historyKeymap, indentWithTab]),
            placeholder('在此编辑 Markdown…'),
            EditorView.lineWrapping,
            EditorView.updateListener.of((u) => {
              if (u.docChanged) {
                onChangeRef.current(u.state.doc.toString())
              }
            }),
            EditorView.updateListener.of((u) => {
              if (!onHybridCursorLineRef.current) return
              if (!u.selectionSet && !u.focusChanged && !u.docChanged) return
              const pos = u.state.selection.main.head
              const line = u.state.doc.lineAt(pos)
              onHybridCursorLineRef.current(line.number - 1)
            }),
            EditorView.theme({
              '&': {
                height: '100%',
                fontSize: '14px',
                fontFamily: 'var(--font-ui, system-ui, sans-serif)'
              },
              '.cm-scroller': { fontFamily: 'inherit' },
              '.cm-content': { paddingBlock: '12px' }
            })
          ]
        })
      })
      viewRef.current = view
      onViewReadyRef.current?.(view)
      return () => {
        onViewReadyRef.current?.(null)
        view.destroy()
        viewRef.current = null
        host.replaceChildren()
      }
    }, [mountKey, showLineNumbers])

    React.useEffect(() => {
      const view = viewRef.current
      if (!view) return
      const cur = view.state.doc.toString()
      if (cur !== value) {
        const main = view.state.selection.main
        view.dispatch({
          changes: { from: 0, to: cur.length, insert: value },
          selection: main.empty ? { anchor: main.from } : { anchor: main.from, head: main.to },
          scrollIntoView: true
        })
      }
    }, [value, mountKey])

    React.useEffect(() => {
      if (!gotoLine) return
      const view = viewRef.current
      if (!view) return
      const lineNo = gotoLine.line + 1
      if (lineNo < 1 || lineNo > view.state.doc.lines) return
      const line = view.state.doc.line(lineNo)
      view.dispatch({ selection: { anchor: line.from }, scrollIntoView: true })
    }, [gotoLine])

    return <div className="editor-host" ref={hostRef} />
  }
)
