import { useState } from 'react';
import Markdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Copy, RefreshCw, Check } from 'lucide-react';
import { toast } from 'sonner';
import type { Message } from '@/api/types';
import { api } from '@/api/client';
import { ToolCallCard } from './ToolCallCard';
import { CodeBlock } from './CodeBlock';

interface Props {
  message: Message;
  sessionId: string;
}

function MarkdownBody({ content }: { content: string }) {
  return (
    <Markdown
      remarkPlugins={[remarkGfm]}
      components={{
        pre: ({ children }) => <>{children}</>,
        code: (props) => {
          const { children, className, ...rest } = props as {
            children?: unknown;
            className?: string;
          };
          const text = String(children ?? '').replace(/\n$/, '');
          const langMatch = /language-([\w-]+)/.exec(className ?? '');
          const isBlock = !!langMatch || text.includes('\n');
          if (isBlock) {
            return <CodeBlock code={text} lang={langMatch?.[1]} />;
          }
          return (
            <code
              {...rest}
              className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]"
            >
              {children as React.ReactNode}
            </code>
          );
        },
        a: ({ children, href }) => (
          <a
            href={href}
            target="_blank"
            rel="noreferrer"
            className="underline decoration-muted-foreground/40 underline-offset-2 hover:decoration-foreground"
          >
            {children}
          </a>
        ),
        ul: ({ children }) => (
          <ul className="list-disc pl-5 space-y-1">{children}</ul>
        ),
        ol: ({ children }) => (
          <ol className="list-decimal pl-5 space-y-1">{children}</ol>
        ),
        p: ({ children }) => <p className="leading-relaxed">{children}</p>,
        h1: ({ children }) => <h1 className="text-base font-semibold mt-2">{children}</h1>,
        h2: ({ children }) => <h2 className="text-sm font-semibold mt-2">{children}</h2>,
        h3: ({ children }) => <h3 className="text-sm font-semibold mt-1">{children}</h3>,
        blockquote: ({ children }) => (
          <blockquote className="border-l-2 border-border pl-3 text-muted-foreground">
            {children}
          </blockquote>
        ),
        table: ({ children }) => (
          <div className="overflow-x-auto">
            <table className="border-collapse text-xs">{children}</table>
          </div>
        ),
        th: ({ children }) => (
          <th className="border border-border px-2 py-1 text-left font-medium">{children}</th>
        ),
        td: ({ children }) => (
          <td className="border border-border px-2 py-1">{children}</td>
        ),
      }}
    >
      {content}
    </Markdown>
  );
}

function StatsLine({ message }: { message: Message }) {
  const tokens = message.tokens_used;
  if (typeof tokens !== 'number' || tokens <= 0) return null;
  const started = message.started_at ? Date.parse(message.started_at) : NaN;
  const finished = message.finished_at ? Date.parse(message.finished_at) : NaN;
  let tps: number | null = null;
  if (!Number.isNaN(started) && !Number.isNaN(finished) && finished > started) {
    const seconds = (finished - started) / 1000;
    if (seconds > 0) tps = Math.round((tokens / seconds) * 10) / 10;
  }
  const ctxUsed = message.ctx_used;
  const ctxMax = message.ctx_max;
  const ctxPart =
    typeof ctxUsed === 'number'
      ? typeof ctxMax === 'number' && ctxMax > 0
        ? `${ctxUsed} / ${ctxMax} ctx`
        : `${ctxUsed} ctx`
      : null;

  const parts: string[] = [`${tokens} tokens`];
  if (tps !== null) parts.push(`${tps.toFixed(1)} tok/s`);
  if (ctxPart) parts.push(ctxPart);

  return (
    <div className="text-[10px] font-mono text-muted-foreground">
      {parts.join(' · ')}
    </div>
  );
}

function ActionRow({
  message,
  sessionId,
}: {
  message: Message;
  sessionId: string;
}) {
  const [justCopied, setJustCopied] = useState(false);
  const [regenerating, setRegenerating] = useState(false);

  async function copy() {
    try {
      await navigator.clipboard.writeText(message.content);
      setJustCopied(true);
      setTimeout(() => setJustCopied(false), 1200);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'copy failed');
    }
  }

  async function regenerate() {
    if (regenerating || message.status === 'streaming') return;
    setRegenerating(true);
    try {
      await api.messages.regenerate(sessionId, message.id);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : 'regenerate failed');
    } finally {
      setRegenerating(false);
    }
  }

  const isAssistant = message.role === 'assistant';
  const canRegen = isAssistant && message.status !== 'streaming';

  return (
    <div className="flex gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
      <button
        type="button"
        onClick={() => void copy()}
        className="inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:bg-muted hover:text-foreground"
        aria-label="Copy message"
        title="Copy"
      >
        {justCopied ? <Check className="size-3" /> : <Copy className="size-3" />}
      </button>
      {isAssistant && (
        <button
          type="button"
          onClick={() => void regenerate()}
          disabled={!canRegen || regenerating}
          className="inline-flex items-center justify-center size-6 rounded text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40 disabled:cursor-not-allowed"
          aria-label="Regenerate message"
          title="Regenerate"
        >
          <RefreshCw className={`size-3 ${regenerating ? 'animate-spin' : ''}`} />
        </button>
      )}
    </div>
  );
}

export function MessageBubble({ message, sessionId }: Props) {
  if (message.role === 'tool') {
    return <ToolCallCard message={message} />;
  }

  if (message.role === 'user') {
    return (
      <div className="group flex flex-col items-end gap-1">
        <div className="max-w-[80%] rounded-lg bg-primary text-primary-foreground px-3 py-2 text-sm whitespace-pre-wrap">
          {message.content}
        </div>
        <ActionRow message={message} sessionId={sessionId} />
      </div>
    );
  }

  const isStreaming = message.status === 'streaming';
  const failed = message.status === 'failed';
  const hasContent = message.content.length > 0;
  const hasToolCalls = (message.tool_calls?.length ?? 0) > 0;

  return (
    <div className="group flex flex-col gap-2">
      {message.tool_calls?.map((tc) => (
        <ToolCallCard key={tc.id} toolCall={tc} />
      ))}
      {(hasContent || (!hasToolCalls && isStreaming)) && (
        <div className="max-w-[90%] text-sm leading-relaxed space-y-2">
          {hasContent ? <MarkdownBody content={message.content} /> : null}
          {isStreaming && (
            <span className="inline-block w-1.5 h-3.5 align-baseline bg-muted-foreground/60 animate-pulse" />
          )}
        </div>
      )}
      {failed && (
        <div className="text-xs text-destructive">message failed</div>
      )}
      {!isStreaming && <StatsLine message={message} />}
      {!isStreaming && (hasContent || hasToolCalls) && (
        <ActionRow message={message} sessionId={sessionId} />
      )}
    </div>
  );
}
