import React, { useCallback, useEffect, useMemo, useState } from "react";

import CodeMirror from "@uiw/react-codemirror";
import { html as htmlLang } from "@codemirror/lang-html";

import { alison, alisonCodemirror } from "../constants/alison";

import { Hint } from "htmlhint/types";

import { HTMLHint } from "htmlhint";
import { occurrences } from "utils/string";
import { debounce } from "utils/debounce";

import classnames from "classnames";
import "./HtmlCodeEditor.scss";

export interface HtmlCodeEditorProps extends React.HTMLProps<HTMLDivElement> {
  html: string;
  isErrorPaneVisible?: boolean;
  onHtmlChange: (html: string) => void;
  validateHtml?: (isValid: boolean) => void;
}

export const HtmlCodeEditor: React.FC<HtmlCodeEditorProps> = ({
  html,
  isErrorPaneVisible = true,
  onHtmlChange,
  validateHtml,
  ...props
}) => {
  const [errors, setErrors] = useState<Hint[]>([]);

  const lines = document.querySelector<HTMLElement>(
    ".komi-code-editor--body-lines",
  );
  const textarea = document.querySelector<HTMLElement>(
    ".komi-code-editor--body-textarea",
  );
  const pre = document.querySelector<HTMLElement>(
    ".komi-code-editor--body-pre",
  );

  // Resize the editor to fit the content. If we don't do this, the editor won't scroll
  // horizontally when the textarea grows. This makes the parent container grow with the
  // textarea
  const handleResize = () => {
    if (textarea && lines) {
      const scrollbarWidth = textarea.offsetWidth - textarea.clientWidth;
      const widthWithScrollbar = textarea.scrollWidth + scrollbarWidth;

      const widthWithScrollbarPx = `${widthWithScrollbar}px`;

      const heightWithScrollbar = textarea.scrollHeight + scrollbarWidth;
      const heightWithScrollbarPx = `${heightWithScrollbar}px`;

      // Set the width of the editor, textarea, and pre to the width of the textarea
      // plus the width of the scrollbar. This will make the editor scroll horizontally
      // when the textarea grows past its initial width
      // Set the height of the textarea and pre to the height of the textarea, excluding
      // the editor container, so that the editor container will scroll vertically and
      // scroll the full-height textarea and pre
      if (textarea) {
        textarea.style.width = widthWithScrollbarPx;
        textarea.style.height = heightWithScrollbarPx;
      }
      if (pre) {
        pre.style.width = widthWithScrollbarPx;
        pre.style.height = heightWithScrollbarPx;
      }
    }
  };

  // Run the resize function on mount and whenever the window is resized
  useEffect(() => {
    // Call the function on initial render
    handleResize();
    // Call the function whenever the window is resized
    window.addEventListener("resize", handleResize);

    // Clean up
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  // Verify the HTML on change
  const htmlVerification = useCallback(
    (changedHtml: string) => {
      const rules = HTMLHint.defaultRuleset; // Use default rules
      const result = HTMLHint.verify(changedHtml, rules);

      setErrors(result);

      if (validateHtml) {
        const isHTMLValid = !result.length;
        validateHtml(isHTMLValid);
      }
    },
    [validateHtml],
  );
  // Debounce the HTML verification so it doesn't run on every keystroke
  const debouncedHtmlVerification = useMemo(
    () => debounce(htmlVerification, 1000),
    [htmlVerification],
  );

  const handleHtmlChange = (changedHtml: string) => {
    debouncedHtmlVerification(changedHtml);
    onHtmlChange(changedHtml);

    handleResize();
  };

  const codeLineCount = occurrences(html, "\n") + 1;
  const lineNumbers = Array.from({ length: codeLineCount }, (x, i) => i);

  const classNames = classnames("komi-code-editor", props.className);

  return (
    <div style={alison.plain} className={classNames}>
      <div className="komi-code-editor--body">
        <CodeMirror
          value={html}
          onChange={handleHtmlChange}
          extensions={[htmlLang()]}
          minHeight="500px"
          theme={alisonCodemirror}
          className="komi-code-editor--body-container-cm"
          basicSetup={{
            foldGutter: false,
            dropCursor: false,
            allowMultipleSelections: false,
          }}
        />
      </div>

      {errors.length > 0 && isErrorPaneVisible && (
        <div className="komi-code-editor--body-errors">
          {errors.map((error, key) => (
            <div className="komi-code-editor--body-error" key={key}>
              <p className="komi-code-editor--body-error-message">
                {error.message}
              </p>
              <span className="komi-code-editor--body-error-location">
                at {error.line}:{error.col}
              </span>
              <p className="komi-code-editor--body-error-evidence">
                in {error.evidence}
              </p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};
