'use client';

import { type ReactNode, useState } from 'react';
import { File as FileGlyph, FileText, X } from 'lucide-react';

import { cn } from '@djangocfg/ui-core/lib';

import { PastedTextDialog } from '../composer/PastedTextDialog';
import { useChatDestructiveStyles } from '../styles';
import type { ChatAttachment } from '../types';

export interface AttachmentRendererArgs {
  attachment: ChatAttachment;
  /** True when shown inside the composer's staging tray (denser layout). */
  isInComposer: boolean;
  onClick?: () => void;
  onRemove?: () => void;
}

export type AttachmentRenderer = (args: AttachmentRendererArgs) => ReactNode;

export interface AttachmentRendererMap {
  text?: AttachmentRenderer;
  image?: AttachmentRenderer;
  audio?: AttachmentRenderer;
  video?: AttachmentRenderer;
  file?: AttachmentRenderer;
  /** Fallback renderer when no per-type entry matched. */
  default?: AttachmentRenderer;
}

interface CommonProps {
  attachments: ChatAttachment[];
  maxVisible?: number;
  onClick?: (a: ChatAttachment) => void;
  onRemove?: (a: ChatAttachment) => void;
  isInComposer?: boolean;
  className?: string;
}

// ---------------------------------------------------------------------------
// AttachmentsGrid — flex-wrap, ideal for thumbnails / file chips.
// ---------------------------------------------------------------------------

export interface AttachmentsGridProps extends CommonProps {
  layout?: 'wrap' | 'grid';
}

export function AttachmentsGrid({
  attachments,
  maxVisible,
  onClick,
  onRemove,
  isInComposer = false,
  layout = 'wrap',
  className,
}: AttachmentsGridProps) {
  if (!attachments?.length) return null;
  const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
  return (
    <div
      className={cn(
        layout === 'grid' ? 'grid grid-cols-3 gap-2' : 'flex flex-wrap gap-2',
        className,
      )}
    >
      {visible.map((a) => (
        <AttachmentTile
          key={a.id}
          attachment={a}
          isInComposer={isInComposer}
          onClick={onClick ? () => onClick(a) : undefined}
          onRemove={onRemove ? () => onRemove(a) : undefined}
        />
      ))}
    </div>
  );
}

// ---------------------------------------------------------------------------
// AttachmentsList — vertical stack, designed for rich custom renderers
// (LazyAudioPlayer, video players, document previews).
// ---------------------------------------------------------------------------

export interface AttachmentsListProps extends CommonProps {
  /** Per-type renderer overrides. Falls back to the default tile. */
  renderers?: AttachmentRendererMap;
}

export function AttachmentsList({
  attachments,
  maxVisible,
  onClick,
  onRemove,
  renderers,
  isInComposer = false,
  className,
}: AttachmentsListProps) {
  if (!attachments?.length) return null;
  const visible = maxVisible ? attachments.slice(0, maxVisible) : attachments;
  return (
    <div className={cn('flex w-full flex-col gap-2', className)}>
      {visible.map((a) => {
        const renderer = renderers?.[a.type] ?? renderers?.default;
        const args: AttachmentRendererArgs = {
          attachment: a,
          isInComposer,
          onClick: onClick ? () => onClick(a) : undefined,
          onRemove: onRemove ? () => onRemove(a) : undefined,
        };
        if (renderer) {
          return (
            <div key={a.id} className="relative w-full min-w-0">
              {renderer(args)}
              {args.onRemove ? <RemoveBtn onRemove={args.onRemove} /> : null}
            </div>
          );
        }
        return <AttachmentTile key={a.id} {...args} />;
      })}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Attachments — backwards-compatible facade. Picks the right component based
// on whether `renderers` are supplied. Existing call-sites keep working.
// ---------------------------------------------------------------------------

export interface AttachmentsProps extends CommonProps {
  layout?: 'grid' | 'row';
  renderers?: AttachmentRendererMap;
}

export function Attachments(props: AttachmentsProps) {
  const { renderers, layout, ...rest } = props;
  if (renderers) {
    return <AttachmentsList {...rest} renderers={renderers} />;
  }
  return <AttachmentsGrid {...rest} layout={layout === 'grid' ? 'grid' : 'wrap'} />;
}

// ---------------------------------------------------------------------------
// Tile + remove btn (default renderers).
// ---------------------------------------------------------------------------

function AttachmentTile({ attachment, onClick, onRemove }: AttachmentRendererArgs) {
  // "Pasted text" chunks are self-contained — they open their own preview
  // dialog on click rather than going through the generic `onClick`.
  if (attachment.type === 'text') {
    return <TextChunkTile attachment={attachment} onRemove={onRemove} />;
  }

  const isImage = attachment.type === 'image';
  const isUploading = attachment.status === 'uploading';

  const inner = isImage ? (
    <img
      src={attachment.thumbnailUrl ?? attachment.url}
      alt={attachment.name ?? 'attachment'}
      // `--radius-control` (~10px) — the design-system token for value
      // chips / controls (see ui-core base.css). Keeps every chip rounded
      // consistently, a touch softer than the old `rounded-md`.
      className="h-16 w-16 rounded-[var(--radius-control)] object-cover"
      loading="lazy"
    />
  ) : (
    <div className="flex max-w-44 items-center gap-2 rounded-[var(--radius-control)] border border-border bg-background/60 px-2 py-1.5 text-xs">
      <FileGlyph aria-hidden className="size-4 shrink-0 text-muted-foreground" />
      <span className="truncate">{attachment.name ?? 'file'}</span>
    </div>
  );

  return (
    <div className="relative">
      {onClick ? (
        // Interactive only when the host wired `onClick` — only then does
        // the chip read as clickable (cursor + hover lift + focus ring).
        <button
          type="button"
          onClick={onClick}
          className="block cursor-pointer rounded-[var(--radius-control)] transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
        >
          {inner}
        </button>
      ) : (
        inner
      )}
      {isUploading ? (
        <div className="pointer-events-none absolute inset-0 flex items-center justify-center rounded-[var(--radius-control)] bg-background/70 text-[10px] font-medium">
          {attachment.progress != null ? `${Math.round(attachment.progress * 100)}%` : '…'}
        </div>
      ) : null}
      {onRemove ? <RemoveBtn onRemove={onRemove} /> : null}
    </div>
  );
}

/**
 * Tile for a `type:'text'` chunk — a two-line "Pasted text" chip in the
 * same surface style as file chips (title + "Pasted text" sublabel).
 * Clicking it opens a read-only `PastedTextDialog` preview of the full
 * payload. Mirrors ChatGPT's pasted-text card + click-to-expand.
 */
function TextChunkTile({
  attachment,
  onRemove,
}: {
  attachment: ChatAttachment;
  onRemove?: () => void;
}) {
  const [open, setOpen] = useState(false);
  const title = attachment.name ?? 'Pasted text';
  return (
    <div className="relative">
      <button
        type="button"
        onClick={() => setOpen(true)}
        aria-label={`Preview ${title}`}
        className="block cursor-pointer rounded-[var(--radius-control)] text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
      >
        <div className="flex max-w-56 items-center gap-2 rounded-[var(--radius-control)] border border-border bg-background/60 px-2 py-1.5 transition-colors hover:bg-background">
          {/* Inner glyph plate keeps the tighter `rounded-md` — a nested
              surface should round a notch less than its container
              (concentric radii) so the corners read cleanly. */}
          <span className="grid size-7 shrink-0 place-items-center rounded-md bg-primary/15 text-primary">
            <FileText aria-hidden className="size-4" />
          </span>
          <span className="flex min-w-0 flex-col">
            <span className="truncate text-xs font-medium">{title}</span>
            <span className="truncate text-[11px] text-muted-foreground">Pasted text</span>
          </span>
        </div>
      </button>
      {onRemove ? <RemoveBtn onRemove={onRemove} /> : null}
      <PastedTextDialog
        open={open}
        onOpenChange={setOpen}
        text={attachment.text ?? ''}
        title={title}
      />
    </div>
  );
}

function RemoveBtn({ onRemove }: { onRemove: () => void }) {
  const styles = useChatDestructiveStyles();
  return (
    <button
      type="button"
      aria-label="Remove attachment"
      onClick={onRemove}
      className={cn(
        'absolute -right-1.5 -top-1.5 grid h-4 w-4 cursor-pointer place-items-center rounded-full border border-border bg-background text-muted-foreground',
        styles.hoverStrong,
      )}
    >
      <X aria-hidden className="size-2.5" />
    </button>
  );
}
