import { markdown } from "@codemirror/lang-markdown";
import { MergeView } from "@codemirror/merge";
import { EditorSelection, EditorState } from "@codemirror/state";
import { drawSelection, EditorView, lineNumbers } from "@codemirror/view";
import { useEffect, useRef } from "react";

interface DiffViewProps {
  before: string;
  after: string;
  beforeLabel: string;
  afterLabel: string;
  focusRange?: { start: number; end: number } | null;
}

const editorTheme = EditorView.theme({
  "&": {
    height: "100%",
    backgroundColor: "#fbfaf7",
    color: "#262822",
    fontSize: "13px",
  },
  ".cm-scroller": {
    fontFamily: '"SFMono-Regular", Consolas, "Liberation Mono", monospace',
    lineHeight: "1.68",
  },
  ".cm-gutters": {
    backgroundColor: "#f2f0ea",
    color: "#8a877e",
    border: "none",
  },
  ".cm-content": {
    padding: "18px 0 36px",
  },
  ".cm-line": {
    padding: "0 14px",
  },
  "&.cm-focused": {
    outline: "2px solid #a7b9ac",
    outlineOffset: "-2px",
  },
});

const readOnlyExtensions = [
  lineNumbers(),
  markdown(),
  EditorState.readOnly.of(true),
  EditorView.editable.of(false),
  drawSelection(),
  EditorView.lineWrapping,
  editorTheme,
];

export function DiffView({ before, after, beforeLabel, afterLabel, focusRange }: DiffViewProps) {
  const host = useRef<HTMLDivElement>(null);
  const merge = useRef<MergeView | null>(null);

  useEffect(() => {
    if (host.current === null) {
      return undefined;
    }
    const view = new MergeView({
      parent: host.current,
      a: { doc: before, extensions: readOnlyExtensions },
      b: { doc: after, extensions: readOnlyExtensions },
      orientation: "a-b",
      gutter: true,
      highlightChanges: true,
    });
    merge.current = view;
    return () => {
      view.destroy();
      merge.current = null;
    };
  }, [before, after]);

  // 切换组件时 stage 文本先到、旧视图上的提前滚动会被重建覆盖，所以聚焦
  // 必须等 MergeView 换成目标文本之后再执行。
  useEffect(() => {
    const view = merge.current;
    if (view === null || focusRange === null || focusRange === undefined) {
      return;
    }
    const anchor = Math.min(Math.max(focusRange.start, 0), view.a.state.doc.length);
    const head = Math.min(Math.max(focusRange.end, anchor), view.a.state.doc.length);
    view.a.dispatch({
      selection: EditorSelection.range(anchor, head),
      effects: EditorView.scrollIntoView(anchor, { y: "center" }),
    });
    view.a.focus();
  }, [focusRange, before, after]);

  return (
    <section className="diff-shell" aria-label={`${beforeLabel}与${afterLabel}对比`}>
      <div className="diff-labels" aria-hidden="true">
        <span>{beforeLabel}</span>
        <span>{afterLabel}</span>
      </div>
      <div className="diff-host" ref={host} />
    </section>
  );
}
