"use client"; import type { ElementType, ReactNode } from "react"; import ReactMarkdown, { type Components } from "react-markdown"; import rehypeKatex from "rehype-katex"; import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import { AlertTriangle, CheckCircle2, Quote } from "lucide-react"; import { AnswerTrustFooter, EvidenceBadge } from "@/components/dashboard/EvidenceBadge"; import type { BrainAskCitation } from "@/lib/api/types"; import type { StudyIntent } from "@/lib/study-intents"; import { mapSuggestedActionToIntent } from "./study-brain-context"; import type { StudyCardMeta } from "./types"; export const markdownComponents: Components = { p: ({ children }) =>

{children}

, ul: ({ children }) => , ol: ({ children }) =>
    {children}
, li: ({ children }) =>
  • {children}
  • , strong: ({ children }) => {children}, em: ({ children }) => {children}, a: ({ children, href }) => ( {children} ), code: ({ children }) => ( {children} ), }; export function Prose({ children }: { children: unknown }) { // Defensive: AI/backend fields are sometimes numbers, arrays, or objects even // when typed as string. Coerce so a non-string never crashes the whole card // tree with "x.trim is not a function". const text = typeof children === "string" ? children.trim() : children == null ? "" : String(children).trim(); if (!text) return null; return ( {text} ); } export function Section({ title, children }: { title: unknown; children: ReactNode }) { const safeTitle = typeof title === "string" ? title : title == null ? "" : String(title); return (

    {safeTitle}

    {children}
    ); } export function BulletList({ items, limit = 8 }: { items: ReadonlyArray; limit?: number }) { // Coerce each item to a string first — backend list fields occasionally // contain numbers/objects, and item.trim() on those crashes the card tree. const clean = (items ?? []) .map((item) => (typeof item === "string" ? item : item == null ? "" : String(item))) .filter((item) => item.trim().length > 0) .slice(0, limit); if (clean.length === 0) return

    DocDoe will add this when the source has enough detail.

    ; return ( ); } export function TrustFooter({ meta }: { meta?: StudyCardMeta }) { if (!meta?.evidenceLabel && !meta?.trustNote && !meta?.isFallback) { return ; } return (
    {meta?.isFallback ? (

    Degraded answer — the AI pipeline used a fallback. Retry or attach a clearer source.

    ) : null} {meta?.evidenceLabel ? (
    ) : null} {meta?.trustNote ?

    {meta.trustNote}

    : null}
    ); } export function CitationBlock({ citations }: { citations: BrainAskCitation[] }) { const visible = citations.filter((citation) => citation.snippet || citation.source_title || citation.heading).slice(0, 5); if (visible.length === 0) return null; return (

    Retrieved evidence

    {visible.map((citation, index) => (
    {citation.source_title ?? "Study material"} {citation.page_number ? ` · p.${citation.page_number}` : ""} {citation.heading ? ` · ${citation.heading}` : ""}
    {citation.snippet ? (

    {citation.snippet}

    ) : null}
    ))}
    ); } export function FollowUpActions({ topic, onAction, meta, }: { topic: string; onAction: (intent: StudyIntent, prompt: string) => void; meta?: StudyCardMeta; }) { const fromApi = (meta?.suggestedActions ?? []) .map((action) => { const mapped = mapSuggestedActionToIntent(action); if (!mapped) return null; return { label: action.length > 42 ? `${action.slice(0, 39)}...` : action, ...mapped }; }) .filter((item): item is { label: string; intent: StudyIntent; prompt: string } => Boolean(item)) .slice(0, 4); const defaults: Array<{ label: string; intent: StudyIntent; prompt: string }> = [ { label: "Explain deeper", intent: "teach_topic", prompt: `Explain ${topic} deeper with examples and exam keywords` }, { label: "Create quiz", intent: "generate_quiz", prompt: `Quiz me on ${topic}` }, { label: "Scoring notes", intent: "generate_notes", prompt: `Make exam-ready notes on ${topic}` }, { label: "Video plan", intent: "create_video_plan", prompt: `Create a study video plan for ${topic}` }, ]; const actions = fromApi.length > 0 ? fromApi : defaults; return (
    {actions.map((action) => ( ))}
    ); } export function RichStudyCard({ icon: Icon, title, eyebrow, children, }: { icon: ElementType; title: string; eyebrow: string; children: ReactNode; }) { return (

    {eyebrow}

    {title}

    {children}
    ); }