import { defaultKeymap } from "@codemirror/commands";
import { css } from "@emotion/react";
import type {
  BasicSetupOptions,
  ReactCodeMirrorProps,
} from "@uiw/react-codemirror";
import CodeMirror, { EditorView, keymap } from "@uiw/react-codemirror";
import React, { useEffect, useMemo, useState } from "react";

import { pierreDark, pierreLight } from "@phoenix/components/code";
import { useTheme } from "@phoenix/contexts";
import { assertUnreachable } from "@phoenix/typeUtils";

import { createTemplateAutocomplete } from "./autocomplete";
import { TemplateFormats } from "./constants";
import { FStringTemplating } from "./language/fString";
import { MustacheLikeTemplating } from "./language/mustacheLike";
import type { TemplateFormat } from "./types";

type TemplateEditorProps = Omit<ReactCodeMirrorProps, "value"> & {
  templateFormat: TemplateFormat;
  defaultValue: string;
  /**
   * Available paths for autocomplete suggestions.
   * When provided, enables autocomplete for template variables.
   */
  availablePaths?: string[];
  /**
   * Accessible name for the editor's textbox
   */
  "aria-label"?: string;
};

const basicSetupOptions: BasicSetupOptions = {
  lineNumbers: true,
  highlightActiveLine: false,
  foldGutter: false,
  highlightActiveLineGutter: false,
  bracketMatching: false,
  defaultKeymap: false,
};

const baseExtensions = [
  EditorView.lineWrapping,
  keymap.of([
    ...defaultKeymap.filter((binding) => binding.key !== "Mod-Enter"),
  ]),
];

/**
 * A template editor that is used to edit the template of a tool.
 *
 * This is an uncontrolled editor.
 * You can only reset the value of the editor by triggering a re-mount, like with the `key` prop,
 * or, when the readOnly prop is true, the editor will reset on all value changes.
 * This is necessary because controlled react-codemirror editors incessantly reset
 * cursor position when value is updated.
 */
export const TemplateEditor = ({
  templateFormat,
  defaultValue,
  readOnly,
  availablePaths,
  "aria-label": ariaLabel = "Prompt template",
  ...props
}: TemplateEditorProps) => {
  const [value, setValue] = useState(() => defaultValue);
  const { theme } = useTheme();
  const codeMirrorTheme = theme === "light" ? pierreLight : pierreDark;
  const extensions = useMemo(() => {
    const ext: TemplateEditorProps["extensions"] = [
      ...baseExtensions,
      // name the textbox for assistive technology
      EditorView.contentAttributes.of({ "aria-label": ariaLabel }),
    ];
    switch (templateFormat) {
      case TemplateFormats.FString:
        ext.push(FStringTemplating());
        break;
      case TemplateFormats.Mustache:
        ext.push(MustacheLikeTemplating());
        break;
      case TemplateFormats.NONE:
        break;
      default:
        assertUnreachable(templateFormat);
    }
    // Add autocomplete extension if available paths are provided and templating is enabled
    if (
      availablePaths &&
      availablePaths.length > 0 &&
      !readOnly &&
      templateFormat !== TemplateFormats.NONE
    ) {
      ext.push(createTemplateAutocomplete(availablePaths, templateFormat));
    }
    return ext;
  }, [templateFormat, availablePaths, readOnly, ariaLabel]);

  useEffect(() => {
    if (readOnly) {
      setValue(defaultValue);
    }
  }, [readOnly, defaultValue]);

  return (
    <CodeMirror
      theme={codeMirrorTheme}
      extensions={extensions}
      basicSetup={basicSetupOptions}
      readOnly={readOnly}
      {...props}
      value={value}
    />
  );
};

export const TemplateEditorWrap = ({
  readOnly,
  children,
}: {
  readOnly?: boolean;
  children: React.ReactNode;
}) => {
  return (
    <div
      css={css`
        & .cm-editor,
        & .cm-gutters {
          background-color: ${!readOnly ? "auto" : "transparent !important"};
        }
        & .cm-gutters {
          border-right: none !important;
        }
        & .cm-content {
          padding: var(--global-dimension-size-100)
            var(--global-dimension-size-250);
        }
        & .cm-gutter,
        & .cm-content {
          min-height: ${!readOnly ? "75px" : "100%"};
        }
        & .cm-line {
          padding-left: 0;
          padding-right: 0;
        }
        & .cm-cursor {
          display: ${!readOnly ? "auto" : "none !important"};
        }
        // Ensure autocomplete tooltip appears above other elements (e.g., chat message cards)
        & .cm-tooltip-autocomplete {
          z-index: 100;
        }
      `}
    >
      {children}
    </div>
  );
};
