import React, { useState } from 'react';
import { FaTrash } from 'react-icons/fa';

import EmptyState from '../agent-analytics/EmptyState';
import type {
  ArticleDetailResponse,
  ArticleSummary,
  SuggestionEntry,
} from '../../service/visibility/visibility.interface';
import {
  deleteArticle,
  generateArticle,
  rejectSuggestion,
} from '../../service/visibility/visibility.service';
import ConfirmDialog from '../ui/ConfirmDialog';
import { deleteArticleTexts } from '../../utils/confirmTexts';

import ArticleModal from './ArticleModal';
import ArticleProgress from './ArticleProgress';
import ArticleSourcePrompts from './ArticleSourcePrompts';
import GenerateLanguagesDialog from './GenerateLanguagesDialog';
import {
  contentTypeLabel,
  contentTypeTone,
  countLanguageSiblings,
  priorityToAccent,
  priorityToLabel,
  priorityToTone,
  toneToBadgeClass,
} from './helpers';

interface RecommendedArticlesListProps {
  suggestions: SuggestionEntry[];
  articles: ArticleSummary[];
  clientId: string;
  token: string;
  brandId: string;
  onArticleReady: (article: ArticleDetailResponse) => void;
  /**
   * Invoked after a suggestion has been rejected on the backend so the
   * parent can drop it from the visible list without a full re-fetch.
   */
  onSuggestionRejected?: (suggestionId: string) => void;
  /**
   * Invoked after a generated article has been hard-deleted so the parent
   * can drop it from its local article list.
   */
  onArticleDeleted?: (articleId: string) => void;
  /**
   * Invoked after the merchant flips an article between ``ready`` and
   * ``published``. The parent should update its local article list so the
   * Recommended/Published tabs re-balance without a refetch.
   */
  onArticlePublishedChanged?: (article: ArticleDetailResponse) => void;
}

/**
 * "Recommended articles" section. Each row surfaces a suggestion + a
 * "Generate" button; clicking dispatches and inlines progress until
 * ``ready``, then flips to "Open article".
 */
const RecommendedArticlesList = ({
  suggestions,
  articles,
  clientId,
  token,
  brandId,
  onArticleReady,
  onSuggestionRejected,
  onArticleDeleted,
  onArticlePublishedChanged,
}: RecommendedArticlesListProps): JSX.Element => {
  // Suggestion awaiting the "which languages?" dialog before its article is
  // dispatched. Null when the dialog is closed.
  const [langDialogSuggestion, setLangDialogSuggestion] =
    useState<SuggestionEntry | null>(null);
  const [pendingArticleBySuggestion, setPendingArticleBySuggestion] = useState<
    Record<string, string>
  >({});
  const [generatedSummaryBySuggestion, setGeneratedSummaryBySuggestion] =
    useState<Record<string, ArticleSummary>>({});
  const [busy, setBusy] = useState<Record<string, boolean>>({});
  const [rejectingBySuggestion, setRejectingBySuggestion] = useState<
    Record<string, boolean>
  >({});
  const [deletingByArticle, setDeletingByArticle] = useState<
    Record<string, boolean>
  >({});
  const [errorBySuggestion, setErrorBySuggestion] = useState<
    Record<string, string>
  >({});
  const [openArticleId, setOpenArticleId] = useState<string | null>(null);
  // Single confirm-modal state shared by both delete entry points - each
  // click closure captures its own ``run`` so the modal stays generic and
  // the side effects remain context-aware.
  const [pendingDelete, setPendingDelete] = useState<{
    title: string;
    run: () => Promise<void>;
  } | null>(null);
  const [pendingDeleteBusy, setPendingDeleteBusy] = useState(false);

  const handleReject = async (suggestion: SuggestionEntry) => {
    setRejectingBySuggestion(previousRejecting => ({
      ...previousRejecting,
      [suggestion.suggestion_id]: true,
    }));
    setErrorBySuggestion(previousErrors => {
      const nextErrors = { ...previousErrors };
      delete nextErrors[suggestion.suggestion_id];
      return nextErrors;
    });
    try {
      await rejectSuggestion(
        clientId,
        token,
        brandId,
        suggestion.suggestion_id
      );
      onSuggestionRejected?.(suggestion.suggestion_id);
    } catch (rejectionError) {
      setErrorBySuggestion(previousErrors => ({
        ...previousErrors,
        [suggestion.suggestion_id]:
          rejectionError instanceof Error
            ? rejectionError.message
            : 'Failed to remove this idea.',
      }));
    } finally {
      setRejectingBySuggestion(previousRejecting => {
        const nextRejecting = { ...previousRejecting };
        delete nextRejecting[suggestion.suggestion_id];
        return nextRejecting;
      });
    }
  };

  /**
   * When the modal hard-deletes the article it's showing, look up which
   * suggestion (if any) cached that article id locally and clear the cache
   * before forwarding the event to the parent.
   *
   * @param deletedArticleId {string} Identifier of the article just hard-deleted on the backend.
   * @returns {void}
   */
  const handleModalDeleted = (deletedArticleId: string) => {
    const owningSuggestionId = Object.keys(generatedSummaryBySuggestion).find(
      suggestionId =>
        generatedSummaryBySuggestion[suggestionId].article_id ===
        deletedArticleId
    );
    if (owningSuggestionId) {
      setGeneratedSummaryBySuggestion(previousCache => {
        const nextCache = { ...previousCache };
        delete nextCache[owningSuggestionId];
        return nextCache;
      });
    }
    onArticleDeleted?.(deletedArticleId);
  };

  /**
   * Hard-delete a generated article and flip the row back to "Generate
   * article". Confirms with the merchant first since the operation also
   * cascades the article's full version history.
   *
   * @param suggestion {SuggestionEntry} The suggestion row whose article is being removed.
   * @param article {ArticleSummary} The ``ready`` article tied to the suggestion.
   * @returns {Promise<void>} Resolves once the delete request and local-state cleanup have finished.
   */
  const handleDelete = (
    suggestion: SuggestionEntry,
    article: ArticleSummary
  ) => {
    const title = article.title || suggestion.title || 'this content';
    setPendingDelete({
      title,
      run: () => runDeleteSuggestionArticle(suggestion, article),
    });
  };

  const runDeleteSuggestionArticle = async (
    suggestion: SuggestionEntry,
    article: ArticleSummary
  ) => {
    setDeletingByArticle(previousDeleting => ({
      ...previousDeleting,
      [article.article_id]: true,
    }));
    setErrorBySuggestion(previousErrors => {
      const nextErrors = { ...previousErrors };
      delete nextErrors[suggestion.suggestion_id];
      return nextErrors;
    });
    try {
      await deleteArticle(clientId, token, article.article_id);
      setGeneratedSummaryBySuggestion(previousCache => {
        const nextCache = { ...previousCache };
        delete nextCache[suggestion.suggestion_id];
        return nextCache;
      });
      onArticleDeleted?.(article.article_id);
    } catch (deleteError) {
      setErrorBySuggestion(previousErrors => ({
        ...previousErrors,
        [suggestion.suggestion_id]:
          deleteError instanceof Error
            ? deleteError.message
            : 'Failed to delete this content.',
      }));
    } finally {
      setDeletingByArticle(previousDeleting => {
        const nextDeleting = { ...previousDeleting };
        delete nextDeleting[article.article_id];
        return nextDeleting;
      });
    }
  };

  /**
   * Fire the staged delete after the user confirms in the modal.
   *
   * @returns {Promise<void>} Resolves once the wrapped runner finishes and
   *     the modal state has been reset.
   */
  const runPendingDelete = async (): Promise<void> => {
    if (!pendingDelete) return;
    setPendingDeleteBusy(true);
    try {
      await pendingDelete.run();
    } finally {
      setPendingDeleteBusy(false);
      setPendingDelete(null);
    }
  };

  /**
   * Backdrop/Esc/cancel handler for the delete-confirm modal. Ignores close
   * requests while the runner is in flight to avoid orphaning the spinner.
   *
   * @param next {boolean} Requested open state from the modal.
   * @returns {void}
   */
  const handlePendingDeleteOpenChange = (next: boolean): void => {
    if (!next && !pendingDeleteBusy) setPendingDelete(null);
  };

  /**
   * Stage a delete-only confirm for an already-generated article (no parent
   * suggestion cleanup). Selected from the trash icon on the article card.
   *
   * @param article {ArticleSummary} Article to schedule for deletion.
   * @returns {void}
   */
  const handleDeleteArticleOnly = (article: ArticleSummary): void => {
    setPendingDelete({
      title: article.title || 'this content',
      run: () => runDeleteArticleOnly(article),
    });
  };

  /**
   * Hard-delete a standalone article without touching suggestion-side caches.
   *
   * @param article {ArticleSummary} Article to remove.
   * @returns {Promise<void>} Resolves once the delete request finishes.
   */
  const runDeleteArticleOnly = async (
    article: ArticleSummary
  ): Promise<void> => {
    setDeletingByArticle(prev => ({
      ...prev,
      [article.article_id]: true,
    }));
    try {
      await deleteArticle(clientId, token, article.article_id);
      onArticleDeleted?.(article.article_id);
    } finally {
      setDeletingByArticle(prev => {
        const next = { ...prev };
        delete next[article.article_id];

        return next;
      });
    }
  };

  /**
   * Resolve the in-flight article id for a suggestion. Reads the backend
   * status first (an article linked to this suggestion that is still
   * ``pending`` / ``generating``) so the row keeps showing "Generating…"
   * across reloads, tab switches and generations kicked off elsewhere - e.g.
   * by the Reco copilot - not just within this component's local session.
   * Falls back to the just-dispatched local id before the article list has
   * refetched.
   *
   * @param suggestionId {string} Suggestion to look up.
   * @returns {string | undefined} The generating article id, if any.
   */
  const generatingArticleIdFor = (suggestionId: string): string | undefined => {
    const backendGenerating = articles.find(
      article =>
        article.triggered_by_suggestion_id === suggestionId &&
        (article.status === 'pending' || article.status === 'generating')
    );

    return (
      backendGenerating?.article_id ?? pendingArticleBySuggestion[suggestionId]
    );
  };

  const readyArticleFor = (
    suggestionId: string,
    preferLanguage?: string
  ): ArticleSummary | undefined => {
    const readyForSuggestion = articles.filter(
      article =>
        article.triggered_by_suggestion_id === suggestionId &&
        article.status === 'ready'
    );
    if (readyForSuggestion.length) {
      // For a multi-language set, open the primary (the language the merchant
      // picked) rather than whichever translation finished most recently;
      // the switcher inside the modal still reaches the other versions.
      const primary = preferLanguage
        ? readyForSuggestion.find(
            article =>
              (article.language || '').toLowerCase() ===
              preferLanguage.toLowerCase()
          )
        : undefined;

      return primary ?? readyForSuggestion[0];
    }
    const cached = generatedSummaryBySuggestion[suggestionId];
    if (cached && cached.status === 'ready') return cached;

    return undefined;
  };

  const handleGenerate = async (
    suggestion: SuggestionEntry,
    languages: string[]
  ) => {
    setLangDialogSuggestion(null);
    setBusy(prev => ({ ...prev, [suggestion.suggestion_id]: true }));
    setErrorBySuggestion(prev => {
      const next = { ...prev };
      delete next[suggestion.suggestion_id];

      return next;
    });
    try {
      const dispatch = await generateArticle(clientId, token, {
        brand_id: brandId,
        topic: suggestion.topic || suggestion.title,
        target_prompts: suggestion.target_prompts,
        keywords: suggestion.keywords,
        language: suggestion.language,
        languages: languages.length ? languages : undefined,
        suggestion_id: suggestion.suggestion_id,
        triggered_by: 'suggestion',
      });
      setPendingArticleBySuggestion(prev => ({
        ...prev,
        [suggestion.suggestion_id]: dispatch.article_id,
      }));
    } catch (err) {
      setErrorBySuggestion(prev => ({
        ...prev,
        [suggestion.suggestion_id]:
          err instanceof Error ? err.message : 'Failed to start generation.',
      }));
    } finally {
      setBusy(prev => ({ ...prev, [suggestion.suggestion_id]: false }));
    }
  };

  // Suggestions whose generated article the merchant already published live
  // in the Published tab - drop them from the Recommended list so the
  // section only surfaces ideas that still need action.
  const publishedSuggestionIds = new Set(
    articles
      .filter(
        article =>
          article.status === 'published' && article.triggered_by_suggestion_id
      )
      .map(article => article.triggered_by_suggestion_id as string)
  );
  const visibleSuggestions = suggestions
    .filter(suggestion => !publishedSuggestionIds.has(suggestion.suggestion_id))
    // Surface brief-driven ideas (source="user") first; the sort is stable so
    // the backend's priority order is preserved within each group.
    .sort((a, b) => Number(b.source === 'user') - Number(a.source === 'user'));

  // Orphan articles - generated outside the suggestion flow (e.g. from a
  // "Where you missed out" loss row, or via the manual one-off
  // generator). They have no parent suggestion to render under, so we
  // surface them as standalone rows.
  const ORPHAN_VISIBLE_STATUSES: ReadonlySet<ArticleSummary['status']> =
    new Set(['pending', 'generating', 'ready']);
  const orphanArticlesRaw = articles
    .filter(
      article =>
        !article.triggered_by_suggestion_id &&
        ORPHAN_VISIBLE_STATUSES.has(article.status)
    )
    .sort((a, b) => (b.created_at ?? '').localeCompare(a.created_at ?? ''));
  // Collapse multi-language siblings into a single card per translation group
  // (the card opens the article, which carries the language-version switcher).
  // Articles without a group are kept as-is.
  const seenOrphanGroups = new Set<string>();
  const orphanArticles = orphanArticlesRaw.filter(article => {
    const groupId = article.translation_group_id;
    if (!groupId) return true;
    if (seenOrphanGroups.has(groupId)) return false;
    seenOrphanGroups.add(groupId);

    return true;
  });

  if (visibleSuggestions.length === 0 && orphanArticles.length === 0) {
    return (
      <EmptyState message="No content suggestions yet. Refresh to generate new ones." />
    );
  }

  return (
    <>
      <div className="flex flex-col gap-3">
        {visibleSuggestions.map(suggestion => {
          const readyArticle = readyArticleFor(
            suggestion.suggestion_id,
            suggestion.language
          );
          const pendingId = generatingArticleIdFor(suggestion.suggestion_id);
          const isGenerating = Boolean(pendingId) && !readyArticle;

          const accent = priorityToAccent(suggestion.priority_score);

          // Language-version set state. A multi-language set must stay in the
          // "generating" state until EVERY language has finished, so the
          // merchant doesn't open a half-translated set; until then each
          // language shows its own little status chip.
          const siblingArticles = articles.filter(
            article =>
              article.triggered_by_suggestion_id === suggestion.suggestion_id
          );
          const isMultiLanguage =
            siblingArticles.some(article => article.translation_group_id) &&
            siblingArticles.length > 1;
          const groupAllReady =
            isMultiLanguage &&
            siblingArticles.every(
              article =>
                article.status === 'ready' || article.status === 'published'
            );
          const groupGenerating =
            isMultiLanguage &&
            !groupAllReady &&
            siblingArticles.some(
              article =>
                article.status === 'pending' || article.status === 'generating'
            );
          // For a multi-language set the whole group gates ready/generating;
          // single-language keeps the original primary-article logic.
          const showReady = isMultiLanguage
            ? groupAllReady
            : Boolean(readyArticle);
          const showGenerating = isMultiLanguage
            ? groupGenerating
            : isGenerating;

          return (
            <div
              key={suggestion.suggestion_id}
              style={{
                borderLeft: `4px solid ${accent}`,
                borderRadius: 10,
                overflow: 'hidden',
              }}
            >
              <div className="bg-white border border-gray-200 border-l-0 rounded-r-xl p-4">
                <div className="flex flex-col gap-4">
                  <div className="flex items-center gap-2 flex-wrap">
                    {suggestion.source === 'user' && (
                      <span
                        className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass('info')}`}
                      >
                        From your brief
                      </span>
                    )}
                    <span
                      aria-hidden="true"
                      className="inline-block w-2 h-2 rounded-full"
                      style={{ background: accent }}
                    />
                    <span className="text-xs text-gray-500">
                      {priorityToLabel(suggestion.priority_score)}
                    </span>
                    <span className="text-xs text-gray-500">·</span>
                    <span
                      className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(priorityToTone(suggestion.priority_score))}`}
                    >
                      {`Score ${suggestion.priority_score}`}
                    </span>
                    {suggestion.estimated_word_count > 0 && (
                      <>
                        <span className="text-xs text-gray-500">·</span>
                        <span className="text-xs text-gray-500">
                          {`~${suggestion.estimated_word_count} words`}
                        </span>
                      </>
                    )}
                  </div>

                  <div className="flex items-start justify-between gap-5 flex-wrap">
                    <div style={{ flex: '1 1 360px', minWidth: 0 }}>
                      <div className="flex flex-col gap-2">
                        <h3 className="text-base font-semibold text-gray-900">
                          {suggestion.title}
                        </h3>
                        {suggestion.rationale && (
                          <p className="text-sm text-gray-600">
                            {suggestion.rationale}
                          </p>
                        )}
                      </div>
                    </div>

                    <div
                      style={{
                        flex: '0 0 auto',
                        alignSelf: 'flex-start',
                        paddingTop: 2,
                      }}
                    >
                      <div className="flex items-center gap-2">
                        {showReady && readyArticle ? (
                          <>
                            <span
                              className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${toneToBadgeClass('success')}`}
                            >
                              Ready
                            </span>
                            {countLanguageSiblings(
                              articles,
                              readyArticle.translation_group_id
                            ) > 1 && (
                              <span
                                className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${toneToBadgeClass('info')}`}
                              >
                                {`Available in ${countLanguageSiblings(
                                  articles,
                                  readyArticle.translation_group_id
                                )} languages`}
                              </span>
                            )}
                            <button
                              type="button"
                              onClick={() =>
                                setOpenArticleId(readyArticle.article_id)
                              }
                              className="rounded-md bg-black px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-800"
                            >
                              Open content
                            </button>
                            <button
                              type="button"
                              onClick={() =>
                                handleDelete(suggestion, readyArticle)
                              }
                              disabled={
                                deletingByArticle[readyArticle.article_id]
                              }
                              aria-label="Delete this content"
                              title="Delete this content"
                              className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700 disabled:opacity-60"
                            >
                              {deletingByArticle[readyArticle.article_id] ? (
                                <span className="text-xs">…</span>
                              ) : (
                                <FaTrash aria-hidden="true" size={14} />
                              )}
                            </button>
                          </>
                        ) : (
                          <button
                            type="button"
                            onClick={() => setLangDialogSuggestion(suggestion)}
                            disabled={
                              showGenerating || busy[suggestion.suggestion_id]
                            }
                            className="rounded-md bg-black px-3 py-1.5 text-sm font-medium text-white hover:bg-gray-800 disabled:opacity-60"
                          >
                            {busy[suggestion.suggestion_id]
                              ? 'Starting...'
                              : showGenerating
                                ? 'Generating…'
                                : 'Generate content'}
                          </button>
                        )}
                        {!showReady && (
                          <button
                            type="button"
                            onClick={() => handleReject(suggestion)}
                            disabled={
                              showGenerating ||
                              busy[suggestion.suggestion_id] ||
                              rejectingBySuggestion[suggestion.suggestion_id]
                            }
                            aria-label="Reject this content idea"
                            title="Reject this content idea"
                            className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-red-200 bg-red-50 text-red-600 hover:bg-red-100 hover:text-red-700 disabled:opacity-60"
                          >
                            {rejectingBySuggestion[suggestion.suggestion_id] ? (
                              <span className="text-xs">…</span>
                            ) : (
                              <FaTrash aria-hidden="true" size={14} />
                            )}
                          </button>
                        )}
                      </div>
                    </div>
                  </div>

                  {errorBySuggestion[suggestion.suggestion_id] && (
                    <p className="text-xs text-red-700">
                      {errorBySuggestion[suggestion.suggestion_id]}
                    </p>
                  )}

                  {isMultiLanguage && showGenerating && (
                    <div className="flex flex-wrap items-center gap-1.5">
                      <span className="text-xs text-gray-500">
                        {`Generating in ${siblingArticles.length} languages:`}
                      </span>
                      {siblingArticles.map(sibling => {
                        const done =
                          sibling.status === 'ready' ||
                          sibling.status === 'published';

                        return (
                          <span
                            key={sibling.article_id}
                            className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(
                              done ? 'success' : 'attention'
                            )}`}
                          >
                            {`${(sibling.language || '?').toUpperCase()} ${
                              done ? '✓' : '…'
                            }`}
                          </span>
                        );
                      })}
                    </div>
                  )}

                  {!isMultiLanguage && showGenerating && pendingId && (
                    <ArticleProgress
                      articleId={pendingId}
                      clientId={clientId}
                      token={token}
                      onReady={article => {
                        setPendingArticleBySuggestion(prev => {
                          const next = { ...prev };
                          delete next[suggestion.suggestion_id];

                          return next;
                        });
                        setGeneratedSummaryBySuggestion(prev => ({
                          ...prev,
                          [suggestion.suggestion_id]: article.summary,
                        }));
                        onArticleReady(article);
                      }}
                      onFailed={message =>
                        setErrorBySuggestion(prev => ({
                          ...prev,
                          [suggestion.suggestion_id]: message,
                        }))
                      }
                    />
                  )}
                </div>
              </div>
            </div>
          );
        })}

        {/* Standalone articles - generated outside the suggestion flow
            (e.g. from a "Where you missed out" loss row, or via the
            manual one-off generator). */}
        {orphanArticles.map(article => {
          const isReady = article.status === 'ready';
          const orphanScore = Math.round(
            ((article.seo_score || 0) + (article.aeo_score || 0)) / 2
          );
          const orphanAccent = priorityToAccent(orphanScore);

          return (
            <div
              key={article.article_id}
              className="overflow-hidden rounded-[10px] bg-white"
              style={{ borderLeft: `4px solid ${orphanAccent}` }}
            >
              <div className="border border-gray-200 rounded-r-[10px] p-4">
                <div className="mb-3 flex items-center gap-2 flex-wrap">
                  <span
                    aria-hidden="true"
                    className="inline-block w-2 h-2 rounded-full"
                    style={{ background: orphanAccent }}
                  />
                  <span className="text-xs text-gray-500">
                    {priorityToLabel(orphanScore)}
                  </span>
                  <span className="text-xs text-gray-500">·</span>
                  <span
                    className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(priorityToTone(orphanScore))}`}
                  >
                    {`Score ${orphanScore}`}
                  </span>
                  {article.content_type && (
                    <>
                      <span className="text-xs text-gray-500">·</span>
                      <span
                        className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${toneToBadgeClass(contentTypeTone(article.content_type))}`}
                      >
                        {contentTypeLabel(article.content_type)}
                      </span>
                    </>
                  )}
                  {article.word_count > 0 && (
                    <>
                      <span className="text-xs text-gray-500">·</span>
                      <span className="text-xs text-gray-500">
                        {`~${article.word_count} words`}
                      </span>
                    </>
                  )}
                </div>

                <div className="flex flex-wrap items-start justify-between gap-4">
                  <div className="min-w-0 flex-1">
                    <h3 className="text-base font-semibold text-gray-900">
                      {article.title || 'Generated content'}
                    </h3>
                    <p className="mt-1 text-xs text-gray-500">
                      {isReady
                        ? !article.content_type ||
                          article.content_type === 'blog'
                          ? 'Generated from a missed prompt - open to review or publish.'
                          : 'Ready - open to review or publish.'
                        : 'Generation in progress…'}
                    </p>
                    <ArticleSourcePrompts prompts={article.target_prompts} />
                  </div>
                  <div className="flex flex-shrink-0 items-center gap-2">
                    {isReady ? (
                      <>
                        <span className="rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800">
                          Ready
                        </span>
                        {countLanguageSiblings(
                          articles,
                          article.translation_group_id
                        ) > 1 && (
                          <span
                            className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${toneToBadgeClass('info')}`}
                          >
                            {`Available in ${countLanguageSiblings(
                              articles,
                              article.translation_group_id
                            )} languages`}
                          </span>
                        )}
                        <button
                          type="button"
                          className="inline-flex items-center rounded-md bg-gray-900 px-3 py-1.5 text-xs font-medium text-white hover:bg-gray-800"
                          onClick={() => setOpenArticleId(article.article_id)}
                        >
                          Open content
                        </button>
                        <button
                          type="button"
                          aria-label="Delete this content"
                          className="inline-flex items-center justify-center rounded-md border border-red-200 bg-white px-2 py-1.5 text-red-700 hover:bg-red-50 disabled:opacity-50"
                          disabled={deletingByArticle[article.article_id]}
                          onClick={() => handleDeleteArticleOnly(article)}
                        >
                          ×
                        </button>
                      </>
                    ) : (
                      <span className="rounded-full bg-orange-100 px-2 py-0.5 text-xs font-medium text-orange-800">
                        Generating…
                      </span>
                    )}
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>

      <ArticleModal
        open={openArticleId !== null}
        articleId={openArticleId}
        onClose={() => setOpenArticleId(null)}
        clientId={clientId}
        token={token}
        onDeleted={handleModalDeleted}
        onPublishedChanged={onArticlePublishedChanged}
      />

      <ConfirmDialog
        open={pendingDelete !== null}
        onOpenChange={handlePendingDeleteOpenChange}
        busy={pendingDeleteBusy}
        onConfirm={runPendingDelete}
        {...deleteArticleTexts(pendingDelete?.title ?? '')}
      />

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

export default RecommendedArticlesList;
