import type { ReactNode } from "react";

import {
  Card,
  CardCollapsedPreview,
  CopyToClipboardButton,
  Flex,
} from "@phoenix/components";
import { inlineDividerCSS } from "@phoenix/components/core/styles";
import { GenerativeProviderIcon } from "@phoenix/components/generative";
import {
  ConnectedMarkdownModeSelect,
  MarkdownDisplayProvider,
} from "@phoenix/components/markdown";
import type {
  AttributeMessage,
  AttributePromptTemplate,
} from "@phoenix/openInference/tracing/types";
import { isModelProvider } from "@phoenix/utils/generativeUtils";
import { safelyParseJSON } from "@phoenix/utils/jsonUtils";

import { useSpanInfoCardProps } from "../SpanInfoCardsContext";
import { defaultCardProps } from "./constants";
import { LLMInvocationParams } from "./LLMInvocationParams";
import type { LLMIOView } from "./LLMIOViewSelect";
import { LLMIOViewSelect, useLLMIOView } from "./LLMIOViewSelect";
import { LLMMessagesCollapseToggle } from "./LLMMessagesCollapseToggle";
import { LLMMessagesList } from "./LLMMessagesList";
import { LLMPromptsList } from "./LLMPromptsList";
import { LLMPromptTemplate } from "./LLMPromptTemplate";
import { LLMToolSchemasList } from "./LLMToolSchemasList";
import { MimeTypeCodeBlock } from "./MimeTypeCodeBlock";
import type { SpanIOValue } from "./types";
import {
  formatJSONForCopy,
  formatJSONStringsForCopy,
  formatTextListForCopy,
  getPromptTemplatePreview,
} from "./utils";

/**
 * The input side of an LLM span — the model card with a view select for
 * messages, tools, raw input, and prompts. The prompt template and invocation
 * parameters render as collapsed cards at the top of the input messages.
 */
export function LLMInput({
  modelName,
  provider,
  input,
  inputMessages,
  toolSchemas,
  promptTemplate,
  prompts,
  invocationParameters,
}: {
  /** The name of the model that was invoked */
  modelName: string | null;
  /** The model provider (e.g. openai) used to pick the provider icon */
  provider: string | null;
  /** The raw input value of the span */
  input: SpanIOValue | null;
  inputMessages: AttributeMessage[];
  /** The JSON schemas of the tools available to the LLM */
  toolSchemas: string[];
  promptTemplate: AttributePromptTemplate | null;
  prompts: string[];
  /** The invocation parameters as a JSON string */
  invocationParameters: string;
}) {
  const toolCount = toolSchemas.length;
  let subTitleEl: ReactNode = null;
  if (modelName != null || toolCount > 0) {
    const normalizedProvider = provider?.toUpperCase();
    // Only show a provider icon when the provider is known
    const providerIcon =
      modelName != null &&
      typeof normalizedProvider === "string" &&
      isModelProvider(normalizedProvider) ? (
        <GenerativeProviderIcon provider={normalizedProvider} height={16} />
      ) : null;
    subTitleEl = (
      <Flex direction="row" gap="size-100" alignItems="center">
        {providerIcon}
        {modelName}
        {modelName != null && toolCount > 0 && (
          <span aria-hidden css={inlineDividerCSS} />
        )}
        {toolCount > 0 && `${toolCount} ${toolCount === 1 ? "tool" : "tools"}`}
      </Flex>
    );
  }

  const hasInput = input != null && input.value != null;
  const hasInputMessages = inputMessages.length > 0;
  const hasLLMToolSchemas = toolCount > 0;
  const hasPrompts = prompts.length > 0;
  const hasInvocationParams =
    Object.keys(safelyParseJSON(invocationParameters).json || {}).length > 0;

  const views: LLMIOView[] = [];
  if (hasInputMessages) views.push({ id: "input-messages", label: "Messages" });
  if (hasLLMToolSchemas) views.push({ id: "tools", label: "Tools" });
  if (hasInput) views.push({ id: "input", label: "Raw" });
  if (hasPrompts) views.push({ id: "prompts", label: "Prompts" });
  const { view, setView } = useLLMIOView(views);

  // Collapsed cards shown above the input messages (input-only context)
  const messageLeadingItems = [
    promptTemplate != null && (
      <Card
        key="prompt-template"
        {...defaultCardProps}
        defaultOpen={false}
        title="Prompt Template"
        headerContent={
          <CardCollapsedPreview>
            {getPromptTemplatePreview(promptTemplate)}
          </CardCollapsedPreview>
        }
        extra={
          <CopyToClipboardButton text={formatJSONForCopy(promptTemplate)} />
        }
      >
        <LLMPromptTemplate promptTemplate={promptTemplate} />
      </Card>
    ),
    hasInvocationParams && (
      <LLMInvocationParams
        key="invocation-params"
        invocationParameters={invocationParameters}
      />
    ),
  ].filter(Boolean);

  const isRawView = view === "input" && hasInput;
  const cardProps = useSpanInfoCardProps("input");

  // Whatever the card is showing is what its copy button copies, so the reader
  // never has to switch views to get at the content in front of them
  let copyText: string | null = null;
  switch (view) {
    case "input-messages":
      copyText = formatJSONForCopy(inputMessages);
      break;
    case "tools":
      copyText = formatJSONStringsForCopy(toolSchemas);
      break;
    case "input":
      copyText = input?.value ?? null;
      break;
    case "prompts":
      copyText = formatTextListForCopy(prompts);
      break;
  }

  return (
    <MarkdownDisplayProvider>
      <Card
        {...defaultCardProps}
        {...cardProps}
        title="Input"
        subTitle={subTitleEl}
        extra={
          <Flex direction="row" gap="size-100" alignItems="center">
            {isRawView && <ConnectedMarkdownModeSelect />}
            {view === "input-messages" && (
              <LLMMessagesCollapseToggle scope="input" />
            )}
            {views.length > 0 && (
              <LLMIOViewSelect
                label="Input view"
                views={views}
                value={view ?? ""}
                onChange={setView}
              />
            )}
            {/* the view switch sits immediately before copy, and copy remains
                last so both controls stay in a consistent place */}
            {copyText != null && <CopyToClipboardButton text={copyText} />}
          </Flex>
        }
      >
        {view === "input-messages" && (
          <LLMMessagesList
            messages={inputMessages}
            leadingItems={messageLeadingItems}
          />
        )}
        {view === "tools" && <LLMToolSchemasList toolSchemas={toolSchemas} />}
        {isRawView && <MimeTypeCodeBlock {...input} />}
        {view === "prompts" && <LLMPromptsList prompts={prompts} />}
      </Card>
    </MarkdownDisplayProvider>
  );
}
