import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
  DownloadIcon,
  ArrowLeftIcon,
  ArrowRightIcon,
  ChevronsRightIcon,
  AnalyzeIcon,
  WindowMaximizeIcon,
} from '../ui/icons';
import PanelHeader from '../ui/PanelHeader';
import { formatFileSize } from '../utils/format';
import { resolveDocumentAssetUrl, createAssetView } from '../asset_manager';
import { useAssetNavigator } from '../hooks/useAssetNavigator';
import { describeDocumentSummary } from '../documents/documentSummary';
import { createDocumentActionState } from '../documents/documentActions';
import PreviewZoomOverlay from './PreviewZoomOverlay';
import DocumentSummarySection, {
  TagSection,
  CorrespondentSection,
  sortCorrespondents,
  buildCorrespondentOptions,
} from '../documents/DocumentSummarySection';
import BreadcrumbTrail from '../ui/BreadcrumbTrail';

const MAX_PREVIEW_STACK_ITEMS = 15;

const derivePreviewOrientation = (metadata) => {
  const width = Number(metadata?.width);
  const height = Number(metadata?.height);
  if (Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0) {
    return width >= height ? 'landscape' : 'portrait';
  }
  return 'landscape';
};

const computeStackAngle = (docId, index) => {
  if (index === 0) return 0;
  let hash = 0;
  const source = docId || `stack-${index}`;
  for (let i = 0; i < source.length; i += 1) {
    hash = (hash * 31 + source.charCodeAt(i)) % 997;
  }
  const magnitude = Math.max(3, (hash % 13) + 3);
  const sign = index % 2 === 0 ? 1 : -1;
  return magnitude * sign;
};

const PreviewStack = ({
  items = [],
  maxItems = MAX_PREVIEW_STACK_ITEMS,
  emptyMessage = 'Preview unavailable',
  onItemActivate,
  onOpenPreview,
  onZoomPreview,
}) => {
  const limited = useMemo(() => items.slice(0, maxItems), [items, maxItems]);
  const hasMultiple = limited.length > 1;
  const preparedItems = useMemo(
    () =>
      limited.map((entry, index) => ({
        entry,
        angle: index === 0 ? 0 : computeStackAngle(entry.id, index),
      })),
    [limited],
  );

  if (!limited.length) {
    return <span className="meta">{emptyMessage}</span>;
  }

  return (
    <div className="preview-stack preview-stack--stacked">
      {preparedItems.map(({ entry, angle }, index) => {
        const transform = hasMultiple
          ? `translate(-50%, -50%) rotate(${angle}deg)`
          : 'translate(-50%, -50%)';
        const isFront = index === 0;
        return (
          <div
            key={entry.id || index}
            className={`preview-stack__item orientation-${entry.orientation || 'landscape'}`}
            style={{
              zIndex: preparedItems.length - index,
              transform,
            }}
            aria-hidden={
              hasMultiple && !onItemActivate && !onOpenPreview && !onZoomPreview
                ? 'true'
                : undefined
            }
          >
            <img
              src={entry.url}
              alt={entry.alt}
              className="preview-stack__image"
              onClick={(event) => {
                event.stopPropagation();
                if (isFront) {
                  if (onZoomPreview) {
                    onZoomPreview(entry);
                  } else if (onOpenPreview) {
                    onOpenPreview(entry.id);
                  } else if (onItemActivate) {
                    onItemActivate(entry.id);
                  }
                } else if (onItemActivate) {
                  onItemActivate(entry.id);
                }
              }}
              onKeyDown={(event) => {
                if (!onItemActivate && !onOpenPreview && !onZoomPreview) return;
                if (event.key === 'Enter' || event.key === ' ') {
                  event.preventDefault();
                  event.stopPropagation();
                  if (isFront) {
                    if (onZoomPreview) {
                      onZoomPreview(entry);
                    } else if (onOpenPreview) {
                      onOpenPreview(entry.id);
                    } else {
                      onItemActivate?.(entry.id);
                    }
                  } else {
                    onItemActivate?.(entry.id);
                  }
                }
              }}
            />
          </div>
        );
      })}
    </div>
  );
};

const DetailPanel = ({
  selectedDocuments = [],
  tags = [],
  tagLookupById = new Map(),
  onTagAdd,
  onTagRemove,
  onRegenerateThumbnails,
  onOpenPreview,
  onBulkTagAdd,
  onBulkTagRemove,
  onBulkReanalyze,
  onBulkCorrespondentAdd,
  onBulkCorrespondentRemove,
  onPromoteSelection,
  onUpdateTitle = async () => false,
  onUpdateIssued = async () => false,
  ensureAssetUrl = null,
  getDocumentAsset = () => null,
  ensurePreviewData = () => Promise.resolve(),
  correspondents = [],
  onCorrespondentAdd,
  onCorrespondentRemove,
  resolveApiPath,
  onFolderNavigate = null,
  resolveFolderPath = null,
  onClose = () => {},
}) => {
  const selectedCount = selectedDocuments.length;
  const singleDoc = selectedCount === 1 ? selectedDocuments[0] : null;
  const singleDocId = singleDoc?.id || null;
  const selectionKey = useMemo(
    () => selectedDocuments.map((doc) => doc?.id ?? '').join('|'),
    [selectedDocuments],
  );

  const { downloadHref: singleDownloadHref } = useMemo(
    () =>
      createDocumentActionState({
        document: singleDoc,
        resolveApiPath,
        ensurePreviewData,
        ensureAssetUrl,
        getDocumentAsset,
      }),
    [singleDoc, resolveApiPath, ensurePreviewData, ensureAssetUrl, getDocumentAsset],
  );

  const detailSummary = useMemo(() => describeDocumentSummary(singleDoc), [singleDoc]);

  const headerTitle = useMemo(() => {
    if (selectedCount === 0) {
      return 'Document details';
    }
    if (selectedCount === 1) {
      return detailSummary.title;
    }
    return `${selectedCount} document${selectedCount === 1 ? '' : 's'}`;
  }, [selectedCount, detailSummary]);

  const headerBreadcrumbs = useMemo(() => {
    if (selectedCount !== 1 || !singleDoc || typeof resolveFolderPath !== 'function') {
      return null;
    }

    const handleNavigate = (folderId) => {
      if (!folderId || typeof onFolderNavigate !== 'function') {
        return;
      }
      onFolderNavigate(folderId);
    };

    const folderSegments = resolveFolderPath(singleDoc.folder_id);
    const normalizedSegments = Array.isArray(folderSegments)
      ? folderSegments
          .filter((segment) => segment && segment.id && segment.name)
          .map((segment) => ({
            id: segment.id,
            label: segment.name,
            onClick: segment.id ? () => handleNavigate(segment.id) : null,
          }))
      : [];

    const documentLabel = detailSummary.title || 'Document';
    return [
      ...normalizedSegments,
      {
        id: singleDoc.id || 'current-document',
        label: documentLabel,
      },
    ];
  }, [selectedCount, singleDoc, resolveFolderPath, detailSummary, onFolderNavigate]);

  const [zoomedPreview, setZoomedPreview] = useState(null);

  const bulkDocumentIds = useMemo(
    () => selectedDocuments.map((doc) => doc?.id).filter(Boolean),
    [selectedDocuments],
  );

  useEffect(() => {
    setZoomedPreview(null);
  }, [selectionKey]);

  const handlePreviewActivate = useCallback(
    (docId) => {
      if (!docId) return;
      onPromoteSelection?.(docId);
    },
    [onPromoteSelection],
  );

  const singlePreviewNavigator = useAssetNavigator({
    document: singleDoc,
    assetType: 'preview',
    ensureAssetUrl,
    getAsset: getDocumentAsset,
    prefetch: 3,
  });

  const makePreviewItem = useCallback(
    (doc, ordinal = 1) => {
      if (!doc) return null;
      const asset = getDocumentAsset(doc, 'preview');
      const assetView = createAssetView(asset);
      const object = assetView.getObject(ordinal);
      let url = object?.url || null;
      if (!url) {
        url = resolveDocumentAssetUrl(doc, 'preview', {
          ensureAssetUrl,
          getAsset: getDocumentAsset,
          ensureOptions: { start: ordinal, limit: 1 },
          objectOrdinal: ordinal,
        });
      }
      if (!url) {
        return null;
      }
      const metadata = object?.metadata || assetView.getPrimaryMetadata() || {};
      const orientation = derivePreviewOrientation(metadata);
      return {
        id: doc.id,
        url,
        orientation,
        alt: doc.title,
      };
    },
    [ensureAssetUrl, getDocumentAsset],
  );

  const stackDocuments = useMemo(() => {
    if (!selectedDocuments.length) return [];
    const seen = new Set();
    const ordered = [];
    for (let index = selectedDocuments.length - 1; index >= 0; index -= 1) {
      const doc = selectedDocuments[index];
      if (!doc?.id || seen.has(doc.id)) continue;
      seen.add(doc.id);
      ordered.push(doc);
      if (ordered.length >= MAX_PREVIEW_STACK_ITEMS) {
        break;
      }
    }
    return ordered;
  }, [selectedDocuments]);

  const stackTopDocument = stackDocuments[0] || null;
  const stackTopDocId = stackTopDocument?.id || null;
  const stackPreviewNavigator = useAssetNavigator({
    document: stackTopDocument,
    assetType: 'preview',
    ensureAssetUrl,
    getAsset: getDocumentAsset,
    prefetch: 3,
  });

  const singlePreviewItems = useMemo(() => {
    if (!singleDoc) return [];
    const url = singlePreviewNavigator.currentUrl;
    if (!url) {
      return [];
    }
    const orientation = derivePreviewOrientation(singlePreviewNavigator.currentMetadata);
    return [
      {
        id: singleDoc.id,
        url,
        orientation,
        alt: singleDoc.title,
      },
    ];
  }, [singleDoc, singlePreviewNavigator.currentUrl, singlePreviewNavigator.currentMetadata]);

  const stackPreviews = useMemo(() => {
    if (!stackDocuments.length) {
      return [];
    }
    return stackDocuments
      .map((doc) => {
        if (!doc) return null;
        if (stackTopDocument && doc.id === stackTopDocument.id) {
          const url = stackPreviewNavigator.currentUrl;
          if (!url) {
            return null;
          }
          const orientation = derivePreviewOrientation(stackPreviewNavigator.currentMetadata);
          return {
            id: doc.id,
            url,
            orientation,
            alt: doc.title,
          };
        }
        return makePreviewItem(doc, 1);
      })
      .filter(Boolean);
  }, [
    stackDocuments,
    stackTopDocument,
    stackPreviewNavigator.currentUrl,
    stackPreviewNavigator.currentMetadata,
    makePreviewItem,
  ]);

  const singleCardinality = singlePreviewNavigator.cardinality;
  const singleEffectiveCardinality = singleCardinality || (singlePreviewNavigator.currentUrl ? 1 : 0);
  const singleHasPreview = Boolean(singlePreviewNavigator.currentUrl);

  const topDocId = stackTopDocument?.id || null;
  const topCardinality = stackPreviewNavigator.cardinality;
  const topEffectiveCardinality = topCardinality || (stackPreviewNavigator.currentUrl ? 1 : 0);
  const topHasPreview = Boolean(stackPreviewNavigator.currentUrl);

  const bulkTagUnion = useMemo(() => {
    if (!selectedDocuments.length) return [];
    const tagMap = new Map();
    selectedDocuments.forEach((doc) => {
      (doc.tags || []).forEach((tag) => {
        if (!tag?.label) return;
        const label = tag.label.trim();
        if (!label) return;
        if (!tagMap.has(label)) {
          const fallback = tagLookupById.get(tag.id);
          tagMap.set(label, {
            id: tag.id,
            label,
            color: tag.color ?? fallback?.color ?? null,
          });
        }
      });
    });
    return [...tagMap.values()].sort((a, b) => a.label.localeCompare(b.label));
  }, [selectedDocuments, tagLookupById]);

  const stackTotalSizeBytes = useMemo(() => {
    if (!stackPreviews.length) return 0;
    const byId = new Map(selectedDocuments.map((doc) => [doc.id, doc]));
    return stackPreviews.reduce((sum, item) => {
      const source = byId.get(item.id);
      const bytes = source?.current_version?.size_bytes;
      return sum + (typeof bytes === 'number' ? bytes : 0);
    }, 0);
  }, [stackPreviews, selectedDocuments]);

  const correspondentOptions = useMemo(
    () => buildCorrespondentOptions(Array.isArray(correspondents) ? correspondents : []),
    [correspondents],
  );

  const singleCorrespondents = useMemo(() => {
    if (!singleDoc) return [];
    return sortCorrespondents(singleDoc.correspondents || []);
  }, [singleDoc]);

  const bulkCorrespondents = useMemo(() => {
    if (selectedDocuments.length <= 1) {
      const doc = selectedDocuments[0];
      return doc ? sortCorrespondents(doc.correspondents || []) : [];
    }

    const map = new Map();
    selectedDocuments.forEach((doc) => {
      if (!doc?.id) return;
      (doc.correspondents || []).forEach((entry) => {
        if (!entry?.id || typeof entry.name !== 'string') return;
        if (!map.has(entry.id)) {
          map.set(entry.id, {
            id: entry.id,
            name: entry.name,
            documentIds: new Set(),
          });
        }
        map.get(entry.id).documentIds.add(doc.id);
      });
    });

    return [...map.values()]
      .map((entry) => ({
        id: entry.id,
        name: entry.name,
        documentIds: [...entry.documentIds],
        count: entry.documentIds.size,
      }))
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [selectedDocuments]);

  const handleBulkCorrespondentRemove = useCallback(
    (entry) => {
      if (!entry?.id) return;
      if (onBulkCorrespondentRemove) {
        return onBulkCorrespondentRemove({
          assignments: [
            {
              correspondent_id: entry.id,
            },
          ],
          documentIds: entry.documentIds,
        });
      }

      if (!onCorrespondentRemove) return;
      const targets = entry.documentIds && entry.documentIds.length
        ? entry.documentIds
        : selectedDocuments
            .filter((doc) => (doc.correspondents || []).some((item) => item.id === entry.id))
            .map((doc) => doc.id);

      return Promise.all(
        targets.map((documentId) =>
          onCorrespondentRemove({
            documentId,
            correspondentId: entry.id,
          }),
        ),
      ).catch(() => {});
    },
    [onBulkCorrespondentRemove, onCorrespondentRemove, selectedDocuments],
  );

  const openZoomPreview = useCallback((config) => {
    if (!config) return;
    setZoomedPreview({
      mode: config.mode,
      docId: config.docId ?? null,
    });
  }, []);

  const closeZoomPreview = useCallback(() => {
    setZoomedPreview(null);
  }, []);

  const handleSingleZoom = useCallback(
    (entry) => {
      if (!singleHasPreview) return;
      const targetId = entry?.id ?? singleDocId;
      if (!targetId) return;
      openZoomPreview({ mode: 'single', docId: targetId });
    },
    [openZoomPreview, singleHasPreview, singleDocId],
  );

  const handleStackZoom = useCallback(
    (entry) => {
      if (!stackTopDocId || entry?.id !== stackTopDocId) return;
      if (!topHasPreview) return;
      openZoomPreview({ mode: 'stack', docId: stackTopDocId });
    },
    [openZoomPreview, stackTopDocId, topHasPreview],
  );

  const zoomDisplay = useMemo(() => {
    if (!zoomedPreview) {
      return null;
    }

    if (
      zoomedPreview.mode === 'single' &&
      singleDocId &&
      singleDoc &&
      singleHasPreview &&
      zoomedPreview.docId === singleDocId
    ) {
      return {
        url: singlePreviewNavigator.currentUrl,
        alt: singleDoc.title,
        canGoPrev:
          singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoPrev),
        canGoNext:
          singleEffectiveCardinality > 1 && Boolean(singlePreviewNavigator.canGoNext),
        goPrev: singlePreviewNavigator.goPrev,
        goNext: singlePreviewNavigator.goNext,
      };
    }

    if (
      zoomedPreview.mode === 'stack' &&
      stackTopDocId &&
      stackTopDocument &&
      zoomedPreview.docId === stackTopDocId &&
      topHasPreview
    ) {
      return {
        url: stackPreviewNavigator.currentUrl,
        alt: stackTopDocument.title,
        canGoPrev:
          topEffectiveCardinality > 1 && Boolean(stackPreviewNavigator.canGoPrev),
        canGoNext:
          topEffectiveCardinality > 1 && Boolean(stackPreviewNavigator.canGoNext),
        goPrev: stackPreviewNavigator.goPrev,
        goNext: stackPreviewNavigator.goNext,
      };
    }

    return null;
  }, [
    zoomedPreview,
    singleDoc,
    singleDocId,
    singleHasPreview,
    singlePreviewNavigator.currentUrl,
    singlePreviewNavigator.canGoPrev,
    singlePreviewNavigator.canGoNext,
    singlePreviewNavigator.goPrev,
    singlePreviewNavigator.goNext,
    singleEffectiveCardinality,
    stackTopDocument,
    stackTopDocId,
    topHasPreview,
    stackPreviewNavigator.currentUrl,
    stackPreviewNavigator.canGoPrev,
    stackPreviewNavigator.canGoNext,
    stackPreviewNavigator.goPrev,
    stackPreviewNavigator.goNext,
    topEffectiveCardinality,
  ]);

  useEffect(() => {
    if (zoomedPreview && !zoomDisplay) {
      setZoomedPreview(null);
    }
  }, [zoomedPreview, zoomDisplay]);

  const {
    documentId: singleNavigatorDocId,
    asset: singleNavigatorAsset,
    ordinal: singleNavigatorOrdinal,
    canGoPrev: singleNavigatorCanGoPrev,
    canGoNext: singleNavigatorCanGoNext,
    cardinality: singleNavigatorCardinality,
  } = singlePreviewNavigator;

  const {
    documentId: stackNavigatorDocId,
    asset: stackNavigatorAsset,
    ordinal: stackNavigatorOrdinal,
    canGoPrev: stackNavigatorCanGoPrev,
    canGoNext: stackNavigatorCanGoNext,
    cardinality: stackNavigatorCardinality,
  } = stackPreviewNavigator;

  useEffect(() => {
    if (typeof ensureAssetUrl !== 'function') {
      return;
    }

    const warmNavigator = (navigator) => {
      const {
        documentId,
        asset,
        ordinal,
        canGoPrev,
        canGoNext,
        cardinality,
      } = navigator;
      if (!documentId || !asset || !Number.isFinite(ordinal)) {
        return;
      }

      const requests = [];
      if (canGoPrev) {
        const prevOrdinal = Math.max(1, ordinal - 1);
        if (!cardinality || prevOrdinal <= cardinality) {
          requests.push(
            ensureAssetUrl(documentId, asset, {
              start: prevOrdinal,
              limit: 1,
              objectOrdinal: prevOrdinal,
            }),
          );
        }
      }
      if (canGoNext) {
        const nextOrdinal = ordinal + 1;
        if (!cardinality || nextOrdinal <= cardinality) {
          requests.push(
            ensureAssetUrl(documentId, asset, {
              start: nextOrdinal,
              limit: 1,
              objectOrdinal: nextOrdinal,
            }),
          );
        }
      }

      requests.forEach((promise) => promise?.catch?.(() => {}));
    };

    const singleWarmState = {
      documentId: singleNavigatorDocId,
      asset: singleNavigatorAsset,
      ordinal: singleNavigatorOrdinal,
      canGoPrev: singleNavigatorCanGoPrev,
      canGoNext: singleNavigatorCanGoNext,
      cardinality: singleNavigatorCardinality,
    };

    const stackWarmState = {
      documentId: stackNavigatorDocId,
      asset: stackNavigatorAsset,
      ordinal: stackNavigatorOrdinal,
      canGoPrev: stackNavigatorCanGoPrev,
      canGoNext: stackNavigatorCanGoNext,
      cardinality: stackNavigatorCardinality,
    };

    warmNavigator(singleWarmState);
    warmNavigator(stackWarmState);
  }, [
    ensureAssetUrl,
    singleNavigatorDocId,
    singleNavigatorAsset,
    singleNavigatorOrdinal,
    singleNavigatorCanGoPrev,
    singleNavigatorCanGoNext,
    singleNavigatorCardinality,
    stackNavigatorDocId,
    stackNavigatorAsset,
    stackNavigatorOrdinal,
    stackNavigatorCanGoPrev,
    stackNavigatorCanGoNext,
    stackNavigatorCardinality,
  ]);

  const renderSingle = () => {
    if (!singleDoc) {
      return <p className="meta">Select a document to view metadata, tags and actions.</p>;
    }

    const effectiveCardinality =
      singlePreviewNavigator.cardinality || (singlePreviewNavigator.currentUrl ? 1 : 0);
    const canGoPrev = singlePreviewNavigator.canGoPrev;
    const canGoNext = singlePreviewNavigator.canGoNext;
    const hasPreviewImage = Boolean(singlePreviewNavigator.currentUrl);
    const interceptNavPointer = (event) => {
      event.preventDefault();
      event.stopPropagation();
    };

    return (
      <>
        <div className="preview-pane preview-pane--stack">
          <PreviewStack
            items={singlePreviewItems}
            maxItems={1}
            emptyMessage="Preview loading…"
            onItemActivate={handlePreviewActivate}
            onOpenPreview={onOpenPreview}
            onZoomPreview={handleSingleZoom}
          />
          {hasPreviewImage && (effectiveCardinality > 1 || canGoPrev || canGoNext) ? (
            <div className="preview-pane__nav preview-pane__nav--overlay">
              <button
                type="button"
                className="preview-pane__nav-button preview-pane__nav-button--prev"
                onClick={(event) => {
                  event.preventDefault();
                  event.stopPropagation();
                  singlePreviewNavigator.goPrev();
                }}
                onPointerDown={interceptNavPointer}
                onPointerUp={interceptNavPointer}
                onMouseDown={interceptNavPointer}
                onMouseUp={interceptNavPointer}
                disabled={!canGoPrev}
                aria-label="Previous preview"
              >
                <ArrowLeftIcon />
              </button>
              <button
                type="button"
                className="preview-pane__nav-button preview-pane__nav-button--next"
                onClick={(event) => {
                  event.preventDefault();
                  event.stopPropagation();
                  singlePreviewNavigator.goNext();
                }}
                onPointerDown={interceptNavPointer}
                onPointerUp={interceptNavPointer}
                onMouseDown={interceptNavPointer}
                onMouseUp={interceptNavPointer}
                disabled={!canGoNext}
                aria-label="Next preview"
              >
                <ArrowRightIcon />
              </button>
            </div>
          ) : null}
        </div>
        <DocumentSummarySection
          document={singleDoc}
          tagLookupById={tagLookupById}
          tagOptions={tags}
          onTagAdd={(doc, value, extras) => onTagAdd(doc, value, extras)}
          onTagRemove={(docId, tagId) => onTagRemove(docId, tagId)}
          correspondents={singleCorrespondents}
          correspondentOptions={correspondentOptions}
          onCorrespondentAdd={onCorrespondentAdd}
          onCorrespondentRemove={onCorrespondentRemove}
          onUpdateTitle={onUpdateTitle}
          onUpdateIssued={onUpdateIssued}
        />
      </>
    );
  };

  const renderBulk = () => {
    const countLabel = `${selectedCount} document${selectedCount === 1 ? '' : 's'}`;
    const sizeLabel = stackTotalSizeBytes ? formatFileSize(stackTotalSizeBytes) : '—';
    const headerLabel = `${countLabel}${sizeLabel ? ` (${sizeLabel})` : ''}`;
    const topDocIdLocal = topDocId;
    const topCardinalityLocal = topEffectiveCardinality;
    const topHasPreview = Boolean(stackPreviewNavigator.currentUrl);
    const topCanGoPrev = stackPreviewNavigator.canGoPrev;
    const topCanGoNext = stackPreviewNavigator.canGoNext;
    const interceptTopNavPointer = (event) => {
      event.preventDefault();
      event.stopPropagation();
    };
    const documentIds = bulkDocumentIds;

    return (
      <>
        <div className="preview-pane preview-pane--stack">
          <PreviewStack
            items={stackPreviews}
            emptyMessage="No previews available."
            onItemActivate={handlePreviewActivate}
            onOpenPreview={onOpenPreview}
            onZoomPreview={handleStackZoom}
          />
          {topDocIdLocal && topHasPreview && (topCardinalityLocal > 1 || topCanGoPrev || topCanGoNext) ? (
            <div className="preview-pane__nav preview-pane__nav--overlay">
                <button
                  type="button"
                  className="preview-pane__nav-button preview-pane__nav-button--prev"
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    stackPreviewNavigator.goPrev();
                  }}
                  onPointerDown={interceptTopNavPointer}
                  onPointerUp={interceptTopNavPointer}
                  onMouseDown={interceptTopNavPointer}
                  onMouseUp={interceptTopNavPointer}
                  disabled={!topCanGoPrev}
                  aria-label="Previous preview"
                >
                  <ArrowLeftIcon />
                </button>
              <button
                type="button"
                  className="preview-pane__nav-button preview-pane__nav-button--next"
                  onClick={(event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    stackPreviewNavigator.goNext();
                  }}
                  onPointerDown={interceptTopNavPointer}
                  onPointerUp={interceptTopNavPointer}
                  onMouseDown={interceptTopNavPointer}
                  onMouseUp={interceptTopNavPointer}
                  disabled={!topCanGoNext}
                  aria-label="Next preview"
                >
                  <ArrowRightIcon />
                </button>
            </div>
          ) : null}
        </div>
        <h3 style={{ margin: 0 }}>{headerLabel}</h3>
        <TagSection
          tags={bulkTagUnion}
          emptyMessage="No tags assigned."
          onRemove={(tag) => onBulkTagRemove?.({ label: tag.label, documentIds })}
          onAdd={({ value }) =>
            onBulkTagAdd?.({ label: value, input: null, documentIds })
          }
          addPlaceholder="Add tag to selection"
          addButtonLabel="Add tag"
          datalistOptions={tags}
          className="bulk-tags"
        />
        <CorrespondentSection
          entries={bulkCorrespondents}
          onRemove={handleBulkCorrespondentRemove}
          onAdd={({ name }) =>
            onBulkCorrespondentAdd?.({ name, input: null, documentIds })
          }
          addPlaceholder="Add correspondent to selection"
          datalistOptions={correspondentOptions}
          showCount
          className="bulk-correspondents"
        />
      </>
    );
  };

  const isBulkSelection = selectedCount > 1;

  const headerLeading = [
    (
      <button
        key="close"
        type="button"
        className="icon-button"
        onClick={onClose}
        aria-label="Close detail panel"
        title="Close detail panel"
      >
        <ChevronsRightIcon />
      </button>
    ),
  ];

  if (singleDoc) {
    headerLeading.push(
      <button
        key="preview"
        type="button"
        className="icon-button"
        onClick={(event) => {
          event.stopPropagation();
          onOpenPreview(singleDoc.id);
        }}
        aria-label="Open preview"
        title="Open preview"
        disabled={!singleHasPreview}
      >
        <WindowMaximizeIcon />
      </button>,
    );
  }

  const headerActions = [];

  if (isBulkSelection && onBulkReanalyze) {
    headerActions.push(
      <button
        key="bulk-reanalyze"
        type="button"
        className="icon-button"
        onClick={(event) => {
          event.stopPropagation();
          onBulkReanalyze(bulkDocumentIds);
        }}
        aria-label="Re-run analysis for selection"
        title="Re-run analysis for selection"
      >
        <AnalyzeIcon />
      </button>,
    );
  }

  if (singleDoc && singleDownloadHref) {
    headerActions.push(
      <a
        key="download"
        className="icon-button"
        href={singleDownloadHref}
        target="_blank"
        rel="noopener noreferrer"
        aria-label="Download document"
        title="Download document"
        onClick={(event) => event.stopPropagation()}
      >
        <DownloadIcon />
      </a>,
    );
  }

  if (singleDoc) {
    headerActions.push(
      <button
        key="reanalyze"
        type="button"
        className="icon-button"
        onClick={(event) => {
          event.stopPropagation();
          onRegenerateThumbnails(singleDoc.id);
        }}
        aria-label="Re-run analysis"
        title="Re-run analysis"
      >
        <AnalyzeIcon />
      </button>,
    );
  }

  return (
    <>
      <aside className="detail-panel panel">
        <PanelHeader
          leading={headerLeading}
          title={
            headerBreadcrumbs ? (
              <BreadcrumbTrail
                entries={headerBreadcrumbs}
                separator="/"
                className="panel-header__breadcrumbs"
                truncateFromStart
              />
            ) : (
              headerTitle
            )
          }
          titleTag="h3"
          actions={headerActions.length ? headerActions : null}
        />
        <div className="panel-body">
          {selectedCount <= 1 ? renderSingle() : renderBulk()}
        </div>
      </aside>
      <PreviewZoomOverlay
        open={Boolean(zoomDisplay?.url)}
        display={zoomDisplay}
        onClose={closeZoomPreview}
      />
    </>
  );
};

export default DetailPanel;
