import { memo, useCallback } from "react";
import { motion } from "motion/react";
import { Sparkles } from "lucide-react";
import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/ai-elements/reasoning";
import {
  Tool,
  ToolHeader,
  ToolContent,
  ToolInput,
  ToolOutput,
} from "@/components/ai-elements/tool";
import { Shimmer } from "@/components/ai-elements/shimmer";
import { MarkdownRenderer } from "@/components/MarkdownRenderer";
import { getProviderIcon } from "./provider-icon";
import { isVisibleToolCall } from "./tool-utils";
import type { StreamingState } from "./types";

// ---- Helpers ----

function parseToolArguments(
  args: string,
): Record<string, unknown> | string {
  try {
    return JSON.parse(args) as Record<string, unknown>;
  } catch {
    return args;
  }
}

const parsedArgsCache = new Map<string, Record<string, unknown> | string>();
function parseToolArgumentsCached(
  args: string,
): Record<string, unknown> | string {
  const cached = parsedArgsCache.get(args);
  if (cached !== undefined) return cached;
  const result = parseToolArguments(args);
  parsedArgsCache.set(args, result);
  return result;
}

// ---- Component ----

export const StreamingView = memo(function StreamingView({
  stream,
  modelProvider,
}: {
  stream: StreamingState;
  modelProvider?: string;
}) {
  const { phase, thinkText, answerText, toolCalls } = stream;

  const isFinal =
    phase === "summarize" || phase === "completed" || phase === "idle";
  const showCursor = !isFinal && answerText.length > 0;

  const showInitialLoading =
    phase === "idle" &&
    !answerText &&
    !thinkText &&
    toolCalls.length === 0;

  const thinkingMessage = useCallback(
    (streaming: boolean, duration?: number) => (
      <span className="flex items-center gap-2">
        {streaming ? (
          <span className="flex items-center gap-1.5">
            <span className="relative flex size-2">
              <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary/40 opacity-75" />
              <span className="relative inline-flex size-2 rounded-full bg-primary/60" />
            </span>
            <span>Thinking</span>
          </span>
        ) : duration != null ? (
          <span>Thought for {duration}s</span>
        ) : (
          <span>Thinking</span>
        )}
      </span>
    ),
    [],
  );

  return (
    <div className="space-y-3" role="status" aria-live="polite">
      {/* Thinking content */}
      {thinkText && (
        <Reasoning isStreaming={phase === "think"}>
          <ReasoningTrigger getThinkingMessage={thinkingMessage} />
          <ReasoningContent>{thinkText}</ReasoningContent>
        </Reasoning>
      )}

      {/* Tool calls */}
      {toolCalls
        .filter((tc) => isVisibleToolCall(tc.toolName))
        .map((tc) => (
          <Tool key={tc.toolCallId}>
            <ToolHeader
              type="dynamic-tool"
              state={
                tc.error
                  ? "output-error"
                  : tc.output != null
                    ? "output-available"
                    : "input-streaming"
              }
              title={tc.toolName}
              toolName={tc.toolName}
            />
            <ToolContent>
              {tc.arguments && (
                <ToolInput input={parseToolArgumentsCached(tc.arguments)} />
              )}
              {(tc.output || tc.error) && (
                <ToolOutput
                  output={tc.output ?? undefined}
                  errorText={tc.error ?? undefined}
                />
              )}
            </ToolContent>
          </Tool>
        ))}

      {/* Answer text */}
      {answerText && (
        <AnswerBubble
          content={answerText}
          showCursor={showCursor}
          providerName={modelProvider}
        />
      )}

      {/* Initial connecting state */}
      {showInitialLoading && <ConnectingState providerName={modelProvider} />}
    </div>
  );
});

// ---- Sub-components ----

const AnswerBubble = memo(function AnswerBubble({
  content,
  showCursor,
  providerName,
}: {
  content: string;
  showCursor: boolean;
  providerName?: string;
}) {
  const Icon = providerName ? getProviderIcon(providerName) : null;

  return (
    <motion.div
      initial={{ opacity: 0, y: 6 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.25, ease: "easeOut" }}
      className="flex gap-3"
    >
      <AIAvatarStreaming Icon={Icon} />
      <div className="max-w-[85%] space-y-2">
        <div className="relative overflow-hidden rounded-2xl rounded-tl-sm border border-border/40 bg-muted/70 px-4 py-2.5 text-sm leading-relaxed text-foreground shadow-sm">
          <MarkdownRenderer
            content={showCursor ? content + "\u25CA" : content}
          />
          {showCursor && (
            <span className="ml-0.5 inline-block h-4 w-px animate-pulse bg-primary/60 align-middle" />
          )}
        </div>
      </div>
    </motion.div>
  );
});

const ConnectingState = memo(function ConnectingState({
  providerName,
}: {
  providerName?: string;
}) {
  const Icon = providerName ? getProviderIcon(providerName) : null;

  return (
    <motion.div
      initial={{ opacity: 0, y: 6 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.25, ease: "easeOut" }}
      className="flex gap-3"
    >
      <AIAvatarStreaming Icon={Icon} />
      <div className="rounded-2xl rounded-tl-sm border border-border/40 bg-muted/70 px-4 py-2.5 text-sm shadow-sm">
        <span className="flex items-center gap-2">
          <span className="relative flex size-2">
            <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-primary/40 opacity-75" />
            <span className="relative inline-flex size-2 rounded-full bg-primary/60" />
          </span>
          <Shimmer duration={1.5} spread={2}>
            Connecting to AI agent…
          </Shimmer>
        </span>
      </div>
    </motion.div>
  );
});

function AIAvatarStreaming({
  Icon,
}: {
  Icon: React.ComponentType<React.SVGProps<SVGSVGElement>> | null;
}) {
  return (
    <span
      className="mt-0.5 grid size-8 shrink-0 place-items-center rounded-full bg-gradient-to-br from-primary/15 to-primary/5 ring-1 ring-primary/10 shadow-sm"
      aria-hidden="true"
    >
      {Icon ? (
        <Icon className="size-4 text-primary/70" />
      ) : (
        <Sparkles className="size-3.5 text-primary/60" />
      )}
    </span>
  );
}
