/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import { useEffect, useState, useRef } from 'react';

import { minimalSetup } from 'codemirror';
import { EditorState, Compartment } from '@codemirror/state';
import { EditorView, placeholder } from '@codemirror/view';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import copy from 'copy-to-clipboard';
import Tooltip from 'bootstrap/js/dist/tooltip';

import { Editor } from '../types';
import { isDarkTheme } from '@/utils/common';

import { createCodeMirrorAdapter } from './codemirror/adapter';

const editableCompartment = new Compartment();
interface htmlRenderConfig {
  copyText: string;
  copySuccessText: string;
}
export function htmlRender(el: HTMLElement | null, config?: htmlRenderConfig) {
  if (!el) return;
  const { copyText = '', copySuccessText = '' } = config || {
    copyText: 'Copy to clipboard',
    copySuccessText: 'Copied!',
  };
  // Replace all br tags with newlines
  // Fixed an issue where the BR tag in the editor block formula HTML caused rendering errors.
  el.querySelectorAll('p').forEach((p) => {
    if (p.innerHTML.startsWith('$$') && p.innerHTML.endsWith('$$')) {
      const str = p.innerHTML.replace(/<br>/g, '\n');
      p.innerHTML = str;
    }
  });

  // change table style

  el.querySelectorAll('table').forEach((table) => {
    if (
      (table.parentNode as HTMLDivElement)?.classList.contains(
        'table-responsive',
      )
    ) {
      return;
    }

    table.classList.add('table', 'table-bordered');
    const div = document.createElement('div');
    div.className = 'table-responsive';
    table.parentNode?.replaceChild(div, table);
    div.appendChild(table);
  });

  // add rel nofollow for link not includes domain
  el.querySelectorAll('a').forEach((a) => {
    const base = window.location.origin;
    const targetUrl = new URL(a.href, base);

    if (targetUrl.origin !== base) {
      a.rel = 'nofollow';
    }
  });

  // Add copy button to all pre tags
  el.querySelectorAll('pre').forEach((pre) => {
    // Create copy button
    const codeWrap = document.createElement('div');
    codeWrap.className = 'position-relative a-code-wrap';
    const codeTool = document.createElement('div');
    codeTool.className = 'a-code-tool';
    const uniqueId = `a-copy-code-${Date.now().toString().substring(5)}-${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}`;
    const str = `
      <a role="button" class="link-secondary a-copy-code" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="${copyText}" id="${uniqueId}">
        <i class="br bi-copy"></i>
      </a>
    `;
    codeTool.innerHTML = str;

    pre.style.position = 'relative';

    codeWrap.appendChild(codeTool);
    pre.parentNode?.replaceChild(codeWrap, pre);
    codeWrap.appendChild(pre);

    const tooltipTriggerList = el.querySelectorAll('.a-copy-code');

    Array.from(tooltipTriggerList)?.map(
      (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl),
    );

    // Copy pre content on button click
    const copyBtn = codeTool.querySelector('.a-copy-code');
    copyBtn?.addEventListener('click', () => {
      const textToCopy = pre.textContent || '';
      copy(textToCopy);
      // Change tooltip text on copy success
      const tooltipInstance = Tooltip.getOrCreateInstance(`#${uniqueId}`);
      tooltipInstance?.setContent({ '.tooltip-inner': copySuccessText });
      const myTooltipEl = document.querySelector(`#${uniqueId}`);
      myTooltipEl?.addEventListener('hidden.bs.tooltip', () => {
        tooltipInstance.setContent({ '.tooltip-inner': copyText });
      });
    });
  });
}

export const useEditor = ({
  editorRef,
  placeholder: placeholderText,
  autoFocus,
  initialValue,
  onChange,
  onFocus,
  onBlur,
}) => {
  const [editor, setEditor] = useState<Editor | null>(null);
  const isInternalUpdateRef = useRef<boolean>(false);

  const init = async () => {
    const isDark = isDarkTheme();

    const theme = EditorView.theme({
      '&': {
        height: '100%',
        padding: '.375rem .75rem',
      },
      '&.cm-focused': {
        outline: 'none',
      },
      '.cm-content': {
        width: '100%',
      },
      '.cm-line': {
        whiteSpace: 'pre-wrap',
        wordWrap: 'break-word',
      },
      '.ͼ7, .ͼ6': {
        textDecoration: 'none',
      },
      '.cm-cursor': {
        'border-left-color': isDark ? 'white' : 'black',
      },
    });

    const startState = EditorState.create({
      doc: initialValue || '',
      extensions: [
        minimalSetup,
        markdown({
          codeLanguages: languages,
          base: markdownLanguage,
        }),
        theme,
        placeholder(placeholderText),
        EditorView.lineWrapping,
        editableCompartment.of(EditorView.editable.of(true)),
        EditorView.domEventHandlers({
          paste(event) {
            const clipboard = event.clipboardData as DataTransfer;
            const htmlStr = clipboard.getData('text/html');
            const imgRegex =
              /<img([\s\S]*?) src\s*=\s*(['"])([\s\S]*?)\2([^>]*)>/;

            return Boolean(htmlStr.match(imgRegex));
          },
        }),
      ],
    });

    const view = new EditorView({
      parent: editorRef.current,
      state: startState,
    });

    const cm = createCodeMirrorAdapter(view as Editor);

    cm.setReadOnly = (readOnly: boolean) => {
      cm.dispatch({
        effects: editableCompartment.reconfigure(
          EditorView.editable.of(!readOnly),
        ),
      });
    };

    if (autoFocus) {
      setTimeout(() => {
        cm.focus();
      }, 10);
    }

    const originalSetValue = cm.setValue;
    cm.setValue = (newValue: string) => {
      isInternalUpdateRef.current = true;
      originalSetValue.call(cm, newValue);
      setTimeout(() => {
        isInternalUpdateRef.current = false;
      }, 0);
    };

    cm.on('change', () => {
      if (!isInternalUpdateRef.current && onChange) {
        const newValue = cm.getValue();
        onChange(newValue);
      }
    });

    cm.on('focus', () => {
      onFocus?.();
    });

    cm.on('blur', () => {
      onBlur?.();
    });

    setEditor(cm);

    return cm;
  };

  useEffect(() => {
    if (!editorRef.current) {
      return;
    }
    if (editorRef.current.children.length > 0 || editor) {
      return;
    }

    init();
  }, [editor]);
  return editor;
};
