import React, { useRef, useState } from 'react';
import { FaMagic, FaUpload } from 'react-icons/fa';

import type { SuggestionEntry } from '../../service/visibility/visibility.interface';
import {
  generateArticle,
  generateSuggestionsFromBrief,
} from '../../service/visibility/visibility.service';
import { DOCUMENT_ACCEPT, extractDocumentText } from '../../utils/documentText';

import ArticleProgress from './ArticleProgress';
import GenerateLanguagesDialog from './GenerateLanguagesDialog';
import Modal from './Modal';
import { priorityToTone, toneToBadgeClass } from './helpers';

/** Hard cap on the brief sent to the backend (matches the API schema). */
const MAX_BRIEF_CHARS = 10_000;

/** Props for {@link GenerateIdeasModal}. */
interface GenerateIdeasModalProps {
  open: boolean;
  onOpenChange: (next: boolean) => void;
  clientId: string;
  token: string;
  brandId: string;
  /** Brand language, used as the default for the generated ideas. */
  language: string;
  /**
   * Fired with the freshly generated ideas so the parent can merge them into
   * the Recommended list without a refetch.
   */
  onIdeasGenerated: (ideas: SuggestionEntry[]) => void;
  /** Fired after an article is dispatched from an idea so the parent can refresh. */
  onArticleStarted?: () => void;
}

/**
 * "Turn your ideas into articles" modal. The merchant pastes a brief (or
 * uploads a txt/csv/docx that fills the textarea), gets 3 to 5 article ideas
 * back, and can dispatch a full article from each one inline.
 *
 * @param {GenerateIdeasModalProps} props - Modal props.
 * @returns {JSX.Element} The modal.
 */
const GenerateIdeasModal = ({
  open,
  onOpenChange,
  clientId,
  token,
  brandId,
  language,
  onIdeasGenerated,
  onArticleStarted,
}: GenerateIdeasModalProps): JSX.Element => {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [brief, setBrief] = useState('');
  const [fileError, setFileError] = useState<string | null>(null);
  const [generating, setGenerating] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [ideas, setIdeas] = useState<SuggestionEntry[]>([]);
  // Idea awaiting the "which languages?" dialog before its article is
  // dispatched. Null when the dialog is closed.
  const [langDialogIdea, setLangDialogIdea] = useState<SuggestionEntry | null>(
    null
  );
  // dispatch in flight (POST not yet acked) per idea.
  const [startingBySuggestion, setStartingBySuggestion] = useState<
    Record<string, boolean>
  >({});
  // articleId of the generation job in flight per idea (drives the inline
  // progress poller, same one the Recommended list uses).
  const [pendingArticleBySuggestion, setPendingArticleBySuggestion] = useState<
    Record<string, string>
  >({});
  // ideas whose article reached ``ready`` while the modal was open.
  const [readyBySuggestion, setReadyBySuggestion] = useState<
    Record<string, boolean>
  >({});
  const [articleErrorBySuggestion, setArticleErrorBySuggestion] = useState<
    Record<string, string>
  >({});

  const trimmedLength: number = brief.trim().length;
  const canGenerate: boolean = trimmedLength >= 10 && !generating;

  /**
   * Read an attached document into the brief textarea, truncated to the brief
   * budget so the merchant sees exactly what will be sent and can edit it.
   * Plain text and CSV are read directly; .docx is parsed in-browser with
   * mammoth.
   *
   * @param {File | null} file - The selected file, or null when cleared.
   * @returns {Promise<void>} Resolves once the file text has been loaded.
   */
  const handleFile = async (file: File | null): Promise<void> => {
    setFileError(null);
    if (!file) return;
    const result = await extractDocumentText(file, MAX_BRIEF_CHARS);
    if (result.error) {
      setFileError(result.error);

      return;
    }
    setBrief(result.text ?? '');
  };

  /**
   * Generate 3 to 5 article ideas from the brief and surface them below.
   *
   * @returns {Promise<void>} Resolves once the request settles.
   */
  const handleGenerate = async (): Promise<void> => {
    if (!canGenerate) return;
    setGenerating(true);
    setError(null);
    try {
      const response = await generateSuggestionsFromBrief(
        clientId,
        token,
        brandId,
        { brief: brief.trim().slice(0, MAX_BRIEF_CHARS), language }
      );
      setIdeas(response.suggestions);
      onIdeasGenerated(response.suggestions);
      if (response.suggestions.length === 0) {
        setError(
          'No ideas came back for that brief. Add more detail and try again.'
        );
      }
    } catch (generateError) {
      setError(
        generateError instanceof Error
          ? generateError.message
          : 'Failed to generate ideas.'
      );
    } finally {
      setGenerating(false);
    }
  };

  /**
   * Dispatch a full article from one generated idea. The idea is already a
   * persisted suggestion, so generation links to it via ``suggestion_id`` and
   * the Recommended list picks the article up once it is ready.
   *
   * @param {SuggestionEntry} idea - The idea to turn into an article.
   * @param {string[]} languages - Languages to generate the idea in at once.
   * @returns {Promise<void>} Resolves once the dispatch settles.
   */
  const handleGenerateArticle = async (
    idea: SuggestionEntry,
    languages: string[]
  ): Promise<void> => {
    setLangDialogIdea(null);
    setStartingBySuggestion(previous => ({
      ...previous,
      [idea.suggestion_id]: true,
    }));
    setArticleErrorBySuggestion(previous => {
      const next = { ...previous };
      delete next[idea.suggestion_id];

      return next;
    });
    try {
      const dispatch = await generateArticle(clientId, token, {
        brand_id: brandId,
        topic: idea.topic || idea.title,
        target_prompts: idea.target_prompts,
        keywords: idea.keywords,
        language: idea.language,
        // Generate this idea in every chosen language at once; the backend
        // links the rows so the article view can switch between versions.
        languages: languages.length ? languages : undefined,
        suggestion_id: idea.suggestion_id,
        triggered_by: 'suggestion',
      });
      setPendingArticleBySuggestion(previous => ({
        ...previous,
        [idea.suggestion_id]: dispatch.article_id,
      }));
      onArticleStarted?.();
    } catch (articleError) {
      setArticleErrorBySuggestion(previous => ({
        ...previous,
        [idea.suggestion_id]:
          articleError instanceof Error
            ? articleError.message
            : 'Failed to start generation.',
      }));
    } finally {
      setStartingBySuggestion(previous => {
        const next = { ...previous };
        delete next[idea.suggestion_id];

        return next;
      });
    }
  };

  /**
   * Resolve a finished generation: drop the poller, flag the idea as ready
   * and let the parent refresh so the article lands under Recommended.
   *
   * @param {string} suggestionId - The idea whose article just finished.
   * @returns {void}
   */
  const handleArticleReady = (suggestionId: string): void => {
    setPendingArticleBySuggestion(previous => {
      const next = { ...previous };
      delete next[suggestionId];

      return next;
    });
    setReadyBySuggestion(previous => ({ ...previous, [suggestionId]: true }));
    onArticleStarted?.();
  };

  /**
   * Surface a failed generation on the owning idea and clear its poller.
   *
   * @param {string} suggestionId - The idea whose article failed.
   * @param {string} message - Failure message to show.
   * @returns {void}
   */
  const handleArticleFailed = (suggestionId: string, message: string): void => {
    setPendingArticleBySuggestion(previous => {
      const next = { ...previous };
      delete next[suggestionId];

      return next;
    });
    setArticleErrorBySuggestion(previous => ({
      ...previous,
      [suggestionId]: message,
    }));
  };

  return (
    <Modal
      open={open}
      onClose={() => onOpenChange(false)}
      title="Turn your ideas into content"
      size="lg"
      footer={
        <button
          type="button"
          onClick={() => onOpenChange(false)}
          className="rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-50"
        >
          Close
        </button>
      }
    >
      <div className="flex flex-col gap-5">
        <p className="text-sm text-gray-600">
          Turn your own notes into content ideas. Type a brief or upload a
          document, and we&apos;ll suggest 3 to 5 angles tailored to your brand
          voice and the prompts you already track.
        </p>

        <ul className="flex flex-col gap-1.5 rounded-lg border border-gray-200 bg-gray-50 p-3 text-xs text-gray-600">
          <li>
            <span className="font-medium text-gray-900">1. Describe it.</span>{' '}
            Paste your idea, or upload a .txt, .csv or .docx (up to 5 MB) and
            we&apos;ll read it into the box - edit it however you like.
          </li>
          <li>
            <span className="font-medium text-gray-900">
              2. Generate ideas.
            </span>{' '}
            You get 3 to 5 content ideas (titles, angles and target keywords),
            each roughly 1,800-2,000 words when written.
          </li>
          <li>
            <span className="font-medium text-gray-900">
              3. Pick what to write.
            </span>{' '}
            Hit &quot;Generate content&quot; on any idea. It then appears under
            Recommended once it finishes, ready to review and publish.
          </li>
        </ul>

        <div className="flex flex-col gap-2">
          <div className="flex items-center justify-between">
            <label
              htmlFor="ideas-brief"
              className="text-sm font-medium text-gray-900"
            >
              Your idea
            </label>
            <button
              type="button"
              onClick={() => fileInputRef.current?.click()}
              className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-2.5 py-1 text-xs font-medium text-gray-700 hover:bg-gray-50"
            >
              <FaUpload className="h-3 w-3" />
              Upload document
            </button>
            <input
              ref={fileInputRef}
              type="file"
              accept={DOCUMENT_ACCEPT}
              className="hidden"
              onChange={event => {
                void handleFile(event.target.files?.[0] ?? null);
                // Reset so re-selecting the same file fires onChange again.
                event.target.value = '';
              }}
            />
          </div>
          <textarea
            id="ideas-brief"
            value={brief}
            onChange={event =>
              setBrief(event.target.value.slice(0, MAX_BRIEF_CHARS))
            }
            placeholder="Describe the topics, angles or audience you want content for. Or upload a spreadsheet of notes and we'll summarise the ideas."
            rows={6}
            maxLength={MAX_BRIEF_CHARS}
            className="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-pink-500 focus:outline-none focus:ring-1 focus:ring-pink-500"
          />
          <div className="flex items-center justify-between text-xs text-gray-500">
            <span>
              {fileError ? (
                <span className="text-red-600">{fileError}</span>
              ) : (
                '.txt, .csv or .docx, up to 5 MB'
              )}
            </span>
            <span>{`${brief.length} / ${MAX_BRIEF_CHARS}`}</span>
          </div>
        </div>

        {error && (
          <div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700">
            {error}
          </div>
        )}

        <div className="flex justify-end">
          <button
            type="button"
            onClick={handleGenerate}
            disabled={!canGenerate}
            className="inline-flex items-center gap-1.5 rounded-md bg-pink-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-pink-700 disabled:opacity-60"
          >
            <FaMagic className="h-4 w-4" />
            {generating ? 'Generating ideas...' : 'Generate ideas'}
          </button>
        </div>

        {ideas.length > 0 && (
          <div className="flex flex-col gap-3 border-t border-gray-200 pt-4">
            <h3 className="text-sm font-semibold text-gray-900">
              {`${ideas.length} idea${ideas.length === 1 ? '' : 's'}`}
            </h3>
            {ideas.map(idea => {
              const pendingArticleId: string | undefined =
                pendingArticleBySuggestion[idea.suggestion_id];
              const isReady: boolean =
                readyBySuggestion[idea.suggestion_id] ?? false;
              const isStarting: boolean =
                startingBySuggestion[idea.suggestion_id] ?? false;
              const articleError: string | undefined =
                articleErrorBySuggestion[idea.suggestion_id];

              return (
                <div
                  key={idea.suggestion_id}
                  className="flex flex-col gap-3 rounded-lg border border-gray-200 bg-gray-50 p-3"
                >
                  <div className="flex flex-wrap items-start justify-between gap-3">
                    <div className="flex min-w-0 flex-1 flex-col gap-1">
                      <p className="text-sm font-semibold text-gray-900">
                        {idea.title}
                      </p>
                      {idea.rationale && (
                        <p className="text-xs text-gray-600">
                          {idea.rationale}
                        </p>
                      )}
                      <div className="flex flex-wrap items-center gap-2 text-xs text-gray-600">
                        <span
                          className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(
                            priorityToTone(idea.priority_score)
                          )}`}
                        >
                          {`Score ${idea.priority_score}`}
                        </span>
                        {idea.estimated_word_count > 0 && (
                          <span>{`~${idea.estimated_word_count} words`}</span>
                        )}
                      </div>
                    </div>
                    {isReady ? (
                      <span
                        className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(
                          'success'
                        )}`}
                      >
                        Ready in Recommended
                      </span>
                    ) : (
                      <button
                        type="button"
                        disabled={isStarting || Boolean(pendingArticleId)}
                        onClick={() => setLangDialogIdea(idea)}
                        className="rounded-md bg-black px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-60"
                      >
                        {pendingArticleId
                          ? 'Generating...'
                          : isStarting
                            ? 'Starting...'
                            : 'Generate content'}
                      </button>
                    )}
                  </div>

                  {pendingArticleId && (
                    <div className="flex flex-col gap-2 border-t border-gray-200 pt-3">
                      <p className="text-xs text-gray-600">
                        Keep this page open. We&apos;re taking your idea,
                        pulling matching products from your catalog, and
                        drafting the content. This usually takes a minute or
                        two.
                      </p>
                      <ArticleProgress
                        articleId={pendingArticleId}
                        clientId={clientId}
                        token={token}
                        onReady={() => handleArticleReady(idea.suggestion_id)}
                        onFailed={message =>
                          handleArticleFailed(idea.suggestion_id, message)
                        }
                      />
                    </div>
                  )}

                  {isReady && (
                    <p className="text-xs text-gray-600">
                      Done. Find it under Recommended to review and publish.
                    </p>
                  )}

                  {articleError && (
                    <p className="text-xs text-red-600" role="alert">
                      {articleError}
                    </p>
                  )}
                </div>
              );
            })}
          </div>
        )}
      </div>

      <GenerateLanguagesDialog
        open={langDialogIdea !== null}
        onOpenChange={next => {
          if (!next) setLangDialogIdea(null);
        }}
        primaryLanguage={langDialogIdea?.language || language || 'en'}
        busy={
          langDialogIdea
            ? Boolean(startingBySuggestion[langDialogIdea.suggestion_id])
            : false
        }
        onConfirm={languages => {
          if (langDialogIdea) {
            void handleGenerateArticle(langDialogIdea, languages);
          }
        }}
      />
    </Modal>
  );
};

export default GenerateIdeasModal;
