'use client';

import { Fragment, memo, useMemo, type ReactNode } from 'react';
import { Copy, Pencil, RefreshCw, Trash } from 'lucide-react';

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from '@djangocfg/ui-core/components';

import type { ChatMessage, ChatRole } from '../types';

/**
 * A single entry in a message bubble's right-click / long-press context
 * menu. Typed + serializable-ish (icon is a ReactNode, the rest is data).
 *
 * Hosts inject extra items — "Reveal in Finder", "Copy as markdown",
 * "Inspect" — by passing `bubbleMenuItems` / `getBubbleMenuItems` on
 * `<MessageBubble>` (and the matching `<ChatRoot messages.bubbleMenu*>`).
 * The built-in Copy / Regenerate / Delete actions are produced as default
 * items by `buildDefaultBubbleMenuItems` so a host can keep, reorder, or
 * fully replace them.
 */
export interface BubbleMenuItem {
  /** Stable id — used as the React key. */
  id: string;
  label: string;
  icon?: ReactNode;
  /** Fires on click / Enter. Receives the message the menu opened on. */
  onSelect: (message: ChatMessage) => void;
  /** Red destructive styling (e.g. Delete). */
  danger?: boolean;
  /** Render a divider BEFORE this item. */
  separator?: boolean;
  disabled?: boolean;
}

/**
 * Handlers the default Copy / Regenerate / Delete items wire to. Mirrors
 * the existing `<MessageBubble>` props so a host can keep passing the same
 * callbacks it always did and get a context menu instead of a button row.
 *
 * Role-gated to match the old button row: Regenerate shows only on
 * assistant turns, Edit only on user turns, and items whose handler is
 * absent are dropped.
 */
export interface DefaultBubbleMenuHandlers {
  role: ChatRole;
  onCopy?: () => void;
  onRegenerate?: () => void;
  /**
   * Fires the host's composer-edit trigger for a USER message — gated to
   * `role === 'user'` (mirrors the assistant-only Regenerate gate). This
   * does NOT edit in the bubble: the item just hands the message to the
   * host, which loads its text into the real composer and re-runs the turn
   * via `chat.editMessage` on submit. Absent → no Edit item.
   */
  onEdit?: () => void;
  onDelete?: () => void;
}

/**
 * Build the built-in Copy / Regenerate / Delete items as `BubbleMenuItem`s.
 * Hosts compose these with their own extras (see `composeBubbleMenuItems`)
 * — keep them, drop one, reorder, or ignore them entirely and pass a fully
 * custom list. An item is omitted when its handler is undefined; Regenerate
 * is additionally gated to assistant turns.
 */
export function buildDefaultBubbleMenuItems({
  role,
  onCopy,
  onRegenerate,
  onEdit,
  onDelete,
}: DefaultBubbleMenuHandlers): BubbleMenuItem[] {
  const items: BubbleMenuItem[] = [];
  if (onCopy) {
    items.push({
      id: 'copy',
      label: 'Copy',
      icon: <Copy aria-hidden className="size-4" />,
      onSelect: () => onCopy(),
    });
  }
  if (onRegenerate && role === 'assistant') {
    items.push({
      id: 'regenerate',
      label: 'Regenerate',
      icon: <RefreshCw aria-hidden className="size-4" />,
      onSelect: () => onRegenerate(),
    });
  }
  // Edit — user turns only (mirrors the assistant-only Regenerate gate).
  // Fires the host trigger; the actual edit happens in the composer.
  if (onEdit && role === 'user') {
    items.push({
      id: 'edit',
      label: 'Edit',
      icon: <Pencil aria-hidden className="size-4" />,
      onSelect: () => onEdit(),
    });
  }
  if (onDelete) {
    items.push({
      id: 'delete',
      label: 'Delete',
      icon: <Trash aria-hidden className="size-4" />,
      onSelect: () => onDelete(),
      danger: true,
      // Divide built-in destructive action from the rest only when it
      // isn't the very first item (e.g. a copy-only user bubble).
      separator: items.length > 0,
    });
  }
  return items;
}

/**
 * Merge the built-in default items with host-provided extras. Defaults
 * come first, then a divider, then host items — unless the host's first
 * extra already declares its own `separator`. Either side may be empty.
 */
export function composeBubbleMenuItems(
  defaults: BubbleMenuItem[],
  hostItems?: BubbleMenuItem[],
): BubbleMenuItem[] {
  if (!hostItems?.length) return defaults;
  if (!defaults.length) return hostItems;
  const [first, ...restHost] = hostItems;
  const bridged: BubbleMenuItem = first.separator ? first : { ...first, separator: true };
  return [...defaults, bridged, ...restHost];
}

export interface BubbleContextMenuProps {
  message: ChatMessage;
  items: BubbleMenuItem[];
  children: ReactNode;
  /** Wrapper className (defaults to `contents` — adds no layout box). */
  className?: string;
}

/**
 * BubbleContextMenu — wraps a bubble in a Radix context menu (right-click
 * + touch long-press, Esc / arrow-key accessible, all handled by the
 * primitive). Renders `items` as menu rows.
 *
 * No items → renders `children` bare (no trigger overhead). The wrapper is
 * `display:contents` by default so it never perturbs the bubble's layout.
 */
function BubbleContextMenuInner({
  message,
  items,
  children,
  className = 'contents',
}: BubbleContextMenuProps) {
  if (items.length === 0) return <>{children}</>;
  return (
    <ContextMenu>
      <ContextMenuTrigger className={className}>{children}</ContextMenuTrigger>
      <ContextMenuContent className="min-w-40">
        {items.map((item) => (
          <Fragment key={item.id}>
            {item.separator ? <ContextMenuSeparator /> : null}
            <ContextMenuItem
              variant={item.danger ? 'destructive' : 'default'}
              disabled={item.disabled}
              onSelect={() => item.onSelect(message)}
            >
              {item.icon}
              {item.label}
            </ContextMenuItem>
          </Fragment>
        ))}
      </ContextMenuContent>
    </ContextMenu>
  );
}

export const BubbleContextMenu = memo(BubbleContextMenuInner);
BubbleContextMenu.displayName = 'BubbleContextMenu';

/**
 * Resolve the effective menu items for a bubble — combines a static list
 * and/or a per-message builder with the built-in defaults. Used by
 * `<MessageBubble>` so callers can pass any of:
 *   - nothing → defaults only
 *   - `bubbleMenuItems` (static host extras) → defaults + extras
 *   - `getBubbleMenuItems(message, defaults)` → host owns the full list
 */
export function useResolvedBubbleMenuItems(
  message: ChatMessage,
  defaults: BubbleMenuItem[],
  bubbleMenuItems?: BubbleMenuItem[],
  getBubbleMenuItems?: (message: ChatMessage, defaults: BubbleMenuItem[]) => BubbleMenuItem[],
): BubbleMenuItem[] {
  return useMemo(() => {
    if (getBubbleMenuItems) return getBubbleMenuItems(message, defaults);
    return composeBubbleMenuItems(defaults, bubbleMenuItems);
  }, [message, defaults, bubbleMenuItems, getBubbleMenuItems]);
}
