import React, { useRef, useState, useEffect } from 'react';
import TextEditor, { type TextEditorRef, type BlockSelection } from './TextEditor.js';
import FormLabel from './FormLabel.js';
import Lightbulb from './Lightbulb.js';
import {
  Menu,
  Pen,
  Sparkles,
  Info,
  Bug,
  ChevronsUpDown,
  ChevronsDownUp,
  Loader,
} from 'lucide-react';
import useStream from '../utilities/useStream.js';
import { marked } from 'marked';
import { Copy } from 'lucide-react';
import Button from './Button.js';
import TagInput from './TagInput.js';
import ReloadButton from './ReloadButton.js';
import './ContentGenerator.css';

marked.use({ async: false });

export default function ContentGenerator({
  draftId,
  initialText = '',
  setText = (_) => {},
}: {
  draftId: string | undefined;
  initialText?: string;
  setText?: (text: string) => void;
}) {
  const menuRef = useRef<HTMLDivElement>(null);
  const [menuState, setMenuState] = useState<'closed' | 'open' | 'draftOptions' | 'replaceText'>(
    'closed',
  );
  const [selectedText, setSelectedText] = useState<BlockSelection | null>(null);
  const closeMenu = () => {
    setMenuState('closed');
    setSelectedText(null);
  };
  const toggleMenu = () => {
    if (menuState !== 'closed') {
      closeMenu();
    } else {
      if (!editorRef.current?.isCurrentBlockEmpty()) {
        const selection = editorRef.current?.selectCurrentBlock();
        setSelectedText(selection ?? null);
      } else {
        setSelectedText(null);
      }
      setMenuState('open');
    }
  };
  useEffect(() => {
    const buttonArea = document.querySelector('.content-generator__action-button');
    if (!buttonArea) return;

    const classMap: Record<string, string> = {
      draftOptions: 'content-generator__action-button--draft-options-open',
      replaceText: 'content-generator__action-button--replace-text-open',
    };

    const targetClass = classMap[menuState];

    // Remove only the classes that aren't the target
    Object.values(classMap).forEach((cls) => {
      if (cls !== targetClass) {
        buttonArea.classList.remove(cls);
      }
    });

    // Add the target class if it exists
    if (targetClass) {
      buttonArea.classList.add(targetClass);
    }
  }, [menuState]);

  useEffect(() => {
    function handleClickOutside(event: MouseEvent) {
      // Check if click is outside the menu.
      if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
        closeMenu();
      }
    }

    if (menuState !== 'closed') {
      // Only add listener when menu is open.
      document.addEventListener('mousedown', handleClickOutside);
    }

    // Cleanup.
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [menuState]);

  // This data can't be saved back to the Conductor API so store it in local state.
  const [toneAndStyle, setToneAndStyle] = useState<string>('');
  const [termsToExclude, setTermsToExclude] = useState<string[]>([]);
  const [additionalInstructions, setAdditionalInstructions] = useState<string>('');

  const editorRef = useRef<TextEditorRef>(null);
  const setTextRef = useRef(setText);
  setTextRef.current = setText;

  const {
    data: outlineData,
    isLoading: outlineIsLoading,
    error: outlineError,
    stream: outlineStream,
    cancel: outlineCancel,
  } = useStream(
    `/conductor/proxy/v3/content-outline/content-generation?draft_id=${draftId}&cache=false`,
  );
  const {
    data: draftData,
    isLoading: draftIsLoading,
    error: draftError,
    stream: draftStream,
    cancel: draftCancel,
  } = useStream(
    `/conductor/proxy/v3/draft-generation/content-generation?draft_id=${draftId}&cache=false`,
  );
  const {
    data: expandData,
    isLoading: expandIsLoading,
    error: expandError,
    stream: expandStream,
    cancel: expandCancel,
  } = useStream(
    `/conductor/proxy/v3/outline-expansion/content-generation?draft_id=${draftId}&cache=false`,
  );
  const {
    data: shortenData,
    isLoading: shortenIsLoading,
    error: shortenError,
    stream: shortenStream,
    cancel: shortenCancel,
  } = useStream(
    `/conductor/proxy/v3/text-shortener/content-generation?draft_id=${draftId}&cache=false`,
  );

  // Menu buttons.
  const generateOutline = () => {
    editorRef.current?.clear();
    closeMenu();
    outlineStream({
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: '{}',
    });
  };
  useEffect(() => {
    if (outlineData.length) {
      const htmlOutlineData = marked(outlineData) as string;
      editorRef.current?.setText(htmlOutlineData);
    }
  }, [outlineData]);

  const generateDraft = () => {
    editorRef.current?.clear();
    closeMenu();
    draftStream({
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        voice_and_tone: toneAndStyle,
        terms_to_exclude: termsToExclude,
        additional_instructions: additionalInstructions,
      }),
    });
  };
  useEffect(() => {
    if (draftData.length) {
      const htmlDraftData = marked(draftData) as string;
      editorRef.current?.setText(htmlDraftData);
    }
  }, [draftData]);

  const cancelStreams = () => {
    outlineCancel();
    draftCancel();
    expandCancel();
    shortenCancel();
  };

  // Store the pending text update
  const pendingTextRef = useRef<string | null>(null);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
  const handleEditorChange = (newText: string) => {
    // Always store the latest text
    pendingTextRef.current = newText;

    // Clear any existing timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    // Set a delay to apply the text
    timeoutRef.current = setTimeout(() => {
      // Only apply if not loading
      if (!draftIsLoading && !outlineIsLoading && pendingTextRef.current !== null) {
        setTextRef.current(pendingTextRef.current);
        pendingTextRef.current = null;
      }
    }, 300);
  };

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
  // Apply pending changes when loading states change
  useEffect(() => {
    if (!draftIsLoading && !outlineIsLoading && pendingTextRef.current !== null) {
      setTextRef.current(pendingTextRef.current);
      pendingTextRef.current = null;
    }
  }, [draftIsLoading, outlineIsLoading]);

  const stripHtml = (text: string): string => {
    // Replace structural tags with newlines.
    const withNewlines = text
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<\/(p|div|h[1-6]|li)>/gi, '\n');

    // Strip all remaining HTML.
    const parsedText =
      new DOMParser().parseFromString(withNewlines, 'text/html').body.textContent || '';

    return parsedText;
  };

  const expandSelection = () => {
    cancelStreams();
    if (selectedText?.text?.length) {
      setMenuState('replaceText');
      expandStream({
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          outline: stripHtml(editorRef.current?.getText() || ''),
          section: [stripHtml(selectedText.text)],
        }),
      });
    } else {
      closeMenu();
    }
  };

  const shortenSelection = () => {
    cancelStreams();
    if (selectedText?.text?.length) {
      setMenuState('replaceText');
      shortenStream({
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          text: stripHtml(editorRef.current?.getText() || ''),
        }),
      });
    } else {
      closeMenu();
    }
  };

  const streamingOverlay = (
    <div className="content-generator__streaming-overlay">
      <div className="content-generator__streaming-overlay-inner">
        <Sparkles />
        <p>Generating content</p>
        <p className="content-generator__message">
          <Info />
          <strong>Reminder:</strong> Writing Assistant generates content using AI technology, which
          can make mistakes. Be sure to fact-check important information before taking further
          action.
        </p>
        <Button variant="surface" size="1" onClick={cancelStreams}>
          Cancel
        </Button>
      </div>
    </div>
  );

  const errorOverlay = (
    <div className="content-generator__streaming-overlay">
      <div className="content-generator__streaming-overlay-inner">
        <p>Error</p>
        <p className="content-generator__message content-generator__message--error">
          <Bug />
          <strong>Error:</strong> There was an error generating content, please try again.
        </p>
        <Button variant="surface" size="1" onClick={outlineCancel}>
          Close
        </Button>
      </div>
    </div>
  );

  const selectionIsEmpty = editorRef.current?.isEmpty() || editorRef.current?.isCurrentBlockEmpty();

  const menuItemKeyDown = (callback: () => void) => (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      callback();
    }
  };

  const floatingMenu = (
    <div className="content-generator__action-button" ref={menuRef}>
      <Lightbulb aria-label="Generate content with AI" role="button" onClick={toggleMenu} />
      {menuState === 'open' && (
        <ul
          className="content-generator__context-menu"
          data-testid="content-generator__context-menu"
          role="menu"
        >
          <li
            role="menuitem"
            tabIndex={0}
            onClick={generateOutline}
            onKeyDown={menuItemKeyDown(generateOutline)}
          >
            <Menu />
            Replace with generated outline
          </li>
          <li
            role="menuitem"
            tabIndex={0}
            onClick={() => setMenuState('draftOptions')}
            onKeyDown={menuItemKeyDown(() => setMenuState('draftOptions'))}
          >
            <Pen />
            Replace with generated draft
          </li>
          <li
            role="menuitem"
            tabIndex={selectionIsEmpty ? -1 : 0}
            onClick={selectionIsEmpty ? undefined : expandSelection}
            onKeyDown={selectionIsEmpty ? undefined : menuItemKeyDown(expandSelection)}
            aria-disabled={selectionIsEmpty}
            className={selectionIsEmpty ? `content-generator__context-menu-option--disabled` : ``}
          >
            <ChevronsUpDown />
            Expand selection
          </li>
          <li
            role="menuitem"
            tabIndex={selectionIsEmpty ? -1 : 0}
            onClick={selectionIsEmpty ? undefined : shortenSelection}
            onKeyDown={selectionIsEmpty ? undefined : menuItemKeyDown(shortenSelection)}
            aria-disabled={selectionIsEmpty}
            className={selectionIsEmpty ? `content-generator__context-menu-option--disabled` : ``}
          >
            <ChevronsDownUp />
            Shorten selection
          </li>
        </ul>
      )}
      {menuState === 'draftOptions' && (
        <div className="content-generator__context-menu content-generator__context-menu--draft-options">
          <FormLabel htmlFor="tone-and-style">Tone and style</FormLabel>
          <textarea
            data-testid="content-generator__tone-and-style"
            className="content-generator__tone-and-style"
            onChange={(e) => setToneAndStyle(e.target.value)}
            value={toneAndStyle}
            name="tone-and-style"
            aria-label="Tone and style"
            rows={2}
            placeholder="Describe the voice of your content"
          />
          <FormLabel>Terms to exclude</FormLabel>
          <TagInput tags={termsToExclude} setTags={(tags) => setTermsToExclude(tags)} />
          <FormLabel>Additional instructions</FormLabel>
          <textarea
            data-testid="content-generator__tone-and-style"
            className="content-generator__tone-and-style"
            onChange={(e) => setAdditionalInstructions(e.target.value)}
            value={additionalInstructions}
            name="tone-and-style"
            aria-label="Tone and style"
            rows={2}
            placeholder="What else should we know about your content?"
          />
          <div className="content-generator__buttons">
            <Button variant="surface" size="1" onClick={closeMenu}>
              Cancel
            </Button>
            <Button size="1" onClick={generateDraft}>
              Generate content
            </Button>
          </div>
        </div>
      )}
      {menuState === 'replaceText' && (
        <div className="content-generator__context-menu content-generator__context-menu--replace-text">
          <div className="content-generator__buttons">
            {expandIsLoading || shortenIsLoading ? (
              <div className="content-generator__expand-shorten-loading">
                <Loader />
              </div>
            ) : (
              <ReloadButton
                onClick={() => {
                  cancelStreams();
                  if (expandData) {
                    expandSelection();
                  } else if (shortenData) {
                    shortenSelection();
                  }
                }}
              />
            )}
            <Button
              variant="surface"
              size="1"
              onClick={() => {
                closeMenu();
                cancelStreams();
              }}
            >
              Cancel
            </Button>
            <Button
              size="1"
              disabled={expandIsLoading || shortenIsLoading}
              onClick={() => {
                if (expandData && selectedText) {
                  editorRef.current?.replaceSelection(selectedText, expandData);
                } else if (shortenData && selectedText) {
                  editorRef.current?.replaceSelection(selectedText, shortenData);
                }
                closeMenu();
                cancelStreams();
              }}
            >
              Insert text
            </Button>
          </div>
          {(expandError || shortenError) && (
            <p className="content-generator__message content-generator__message--error">
              <Bug />
              <strong>Error:</strong> There was an error generating content, please try again.
            </p>
          )}
          {expandData && (
            <div
              className="content-generator__replace-text"
              dangerouslySetInnerHTML={{ __html: marked(expandData) as string }}
            />
          )}
          {shortenData && (
            <div
              className="content-generator__replace-text"
              dangerouslySetInnerHTML={{ __html: marked(shortenData) as string }}
            />
          )}
        </div>
      )}
    </div>
  );

  if (!draftId) {
    return null;
  }

  return (
    <div
      className={`content-generator ${outlineIsLoading && !outlineError ? 'content-generator--is-streaming' : ''}`}
    >
      <FormLabel id="content-generator-label">Content</FormLabel>
      <TextEditor
        ref={editorRef}
        labelId="content-generator-label"
        initialText={initialText}
        onChange={handleEditorChange}
        floatingMenu={floatingMenu}
        overlay={
          ((outlineError || draftError) && errorOverlay) ||
          ((outlineIsLoading || draftIsLoading) && streamingOverlay) ||
          null
        }
      />
      <Button
        variant="surface"
        onClick={() => {
          const htmlText = editorRef.current?.getText() || '';
          const doc = new DOMParser().parseFromString(htmlText, 'text/html');
          navigator.clipboard.write([
            new ClipboardItem({
              'text/html': new Blob([htmlText], { type: 'text/html' }),
              'text/plain': new Blob([doc.body.textContent || ''], { type: 'text/plain' }),
            }),
          ]);
        }}
      >
        <Copy />
        Copy Text
      </Button>
    </div>
  );
}
