// Renders an assistant message: GFM markdown for prose, plus typed fenced
// code blocks rendered as rich UI from `@crema/*-ui` libs (charts, tables,
// KPIs, code, diffs, status pills, callouts), pills for command-bus action
// blocks, and tool-result cards (role: "tool").
//
// Typed blocks recognized (each is a fenced ```<kind>\n<json>\n``` block):
//   action       — command-bus DSL (handled by extractActionBlocks; replaced
//                  with a "Ran N actions" pill)
//   card         — { kind: "pill" | "stat" | "callout", ... } (legacy)
//   chart-spark  — { values: number[], stroke?, fill? }
//   chart-bar    — { data: [{ label, value, color? }] }
//   chart-line   — { series: [{ x, y }] }
//   chart-donut  — { data: [{ label, value, color? }] }
//   table        — { columns: [{ id, header, accessor? }], rows: [...] }
//   kpi          — { items: [{ label, value, unit? }] }
//   code         — { code, language?, title?, lineNumbers? }
//   diff         — { oldCode, newCode, language?, title? }

import { type ReactNode, useMemo } from "react"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { Sparkles, Wrench } from "lucide-react"

import { extractActionBlocks } from "@crema/action-bus"
import { stripToolCallTags, type ToolCall } from "@crema/llm-ui"
import {
  Sparkline,
  BarChart,
  LineChart,
  Donut,
  type ChartDatum,
  type SeriesPoint,
} from "@crema/chart-ui"
import { DataTable, type Column } from "@crema/table-ui"
import { KPIRow } from "@crema/data-ui"
import { CodeBlock, DiffViewer } from "@crema/code-ui"
import { FlowChart, OrgChart } from "@crema/diagram-ui"
import { StepTrail, type AgentStep } from "@crema/agent-ui"
import {
  OnboardingChecklist,
  WelcomeCard,
  HintCard,
  type ChecklistTask,
  type OnboardingTone,
} from "@crema/onboarding-ui"

const ACTION_BLOCK_RE = /```action\s*\n[\s\S]*?```/g

// Captures the kind tag and the body of every fenced block we render as UI.
// Kept alongside the markdown ones — react-markdown ignores anything we strip.
const TYPED_BLOCK_RE =
  /```(card|chart-spark|chart-bar|chart-line|chart-donut|table|kpi|code|diff|flowchart|orgchart|steps|checklist|welcome|hint)\s*\n([\s\S]*?)```/g

export type MessageBodyProps = {
  content: string
  isToolResult?: boolean
  toolCalls?: ToolCall[]
}

type Segment =
  | { type: "prose"; text: string }
  | { type: "block"; kind: string; spec: unknown; raw: string }

function parseSegments(content: string): Segment[] {
  const segments: Segment[] = []
  TYPED_BLOCK_RE.lastIndex = 0
  let lastIndex = 0
  let match: RegExpExecArray | null
  while ((match = TYPED_BLOCK_RE.exec(content)) !== null) {
    const [raw, kind, body] = match
    if (match.index > lastIndex) {
      segments.push({ type: "prose", text: content.slice(lastIndex, match.index) })
    }
    let spec: unknown = null
    try {
      spec = JSON.parse(body.trim())
    } catch {
      // malformed → emit the raw fence as prose so the user sees the model output
      segments.push({ type: "prose", text: raw })
      lastIndex = match.index + raw.length
      continue
    }
    segments.push({ type: "block", kind, spec, raw })
    lastIndex = match.index + raw.length
  }
  if (lastIndex < content.length) {
    segments.push({ type: "prose", text: content.slice(lastIndex) })
  }
  return segments
}

function renderBlock(kind: string, spec: any, key: number): ReactNode {
  switch (kind) {
    case "card":
      return <CardBlock key={key} spec={spec} />
    case "chart-spark":
      return (
        <div key={key} className="my-2 inline-block text-primary">
          <Sparkline
            values={spec.values ?? []}
            width={spec.width ?? 240}
            height={spec.height ?? 48}
            stroke={spec.stroke ?? "currentColor"}
            fill={spec.fill}
          />
        </div>
      )
    case "chart-bar":
      return (
        <ChartFrame key={key} title={spec.title}>
          <div className="text-primary">
            <BarChart data={(spec.data ?? []) as ChartDatum[]} width={spec.width ?? 360} height={spec.height ?? 180} />
          </div>
          <ChartLegend data={spec.data} />
        </ChartFrame>
      )
    case "chart-line":
      return (
        <ChartFrame key={key} title={spec.title}>
          <div className="text-primary">
            <LineChart series={(spec.series ?? []) as SeriesPoint[]} width={spec.width ?? 360} height={spec.height ?? 180} />
          </div>
        </ChartFrame>
      )
    case "chart-donut":
      return (
        <ChartFrame key={key} title={spec.title}>
          <div className="flex items-center gap-4">
            <div className="text-primary">
              <Donut data={(spec.data ?? []) as ChartDatum[]} size={spec.size ?? 140} thickness={spec.thickness ?? 20} />
            </div>
            <ChartLegend data={spec.data} />
          </div>
        </ChartFrame>
      )
    case "table":
      return <TableBlock key={key} spec={spec} />
    case "kpi":
      return (
        <div key={key} className="my-3">
          <KPIRow items={spec.items ?? []} />
        </div>
      )
    case "code":
      return (
        <div key={key} className="my-3">
          <CodeBlock
            code={spec.code ?? ""}
            language={spec.language}
            title={spec.title}
            showLineNumbers={spec.lineNumbers ?? false}
            highlightLines={spec.highlightLines}
          />
        </div>
      )
    case "diff":
      return (
        <div key={key} className="my-3">
          <DiffViewer
            oldCode={spec.oldCode ?? ""}
            newCode={spec.newCode ?? ""}
            language={spec.language}
            title={spec.title}
            mode={spec.mode ?? "unified"}
          />
        </div>
      )
    case "flowchart":
      return (
        <div key={key} className="my-3 overflow-x-auto rounded-lg border bg-card/50 p-3">
          <FlowChart nodes={spec.nodes ?? []} edges={spec.edges ?? []} />
        </div>
      )
    case "orgchart":
      return (
        <div key={key} className="my-3 overflow-x-auto rounded-lg border bg-card/50 p-3">
          <OrgChart data={spec.data} horizontal={spec.horizontal} />
        </div>
      )
    case "steps":
      return (
        <div key={key} className="my-3 rounded-lg border bg-card/50 p-3">
          <StepTrail steps={(spec.steps ?? []) as AgentStep[]} />
        </div>
      )
    case "checklist":
      return (
        <div key={key} className="my-3">
          <OnboardingChecklist
            title={spec.title}
            description={spec.description}
            tasks={(spec.tasks ?? []) as ChecklistTask[]}
          />
        </div>
      )
    case "welcome":
      return (
        <div key={key} className="my-3">
          <WelcomeCard
            title={spec.title}
            description={spec.description}
            badge={spec.badge}
            primaryAction={spec.primaryAction}
            secondaryAction={spec.secondaryAction}
          />
        </div>
      )
    case "hint":
      return (
        <div key={key} className="my-3">
          <HintCard
            title={spec.title}
            tone={(spec.tone ?? "info") as OnboardingTone}
            action={spec.action}
          >
            {spec.body ?? ""}
          </HintCard>
        </div>
      )
    default:
      return (
        <pre key={key} className="my-2 rounded-md border border-border/60 bg-muted/40 p-2 text-[11px] font-mono text-muted-foreground">
          {JSON.stringify(spec, null, 2)}
        </pre>
      )
  }
}

function ChartFrame({ title, children }: { title?: string; children: ReactNode }) {
  return (
    <div className="my-3 rounded-lg border bg-card/50 p-3">
      {title && <div className="mb-2 text-xs font-medium text-muted-foreground">{title}</div>}
      {children}
    </div>
  )
}

function ChartLegend({ data }: { data?: ChartDatum[] }) {
  if (!data || data.length === 0) return null
  return (
    <ul className="mt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
      {data.map((d) => (
        <li key={d.label} className="flex items-center gap-1.5">
          <span
            className="inline-block size-2 rounded-sm"
            style={{ background: d.color ?? "currentColor" }}
          />
          <span>{d.label}</span>
          <span className="tabular-nums text-foreground/70">{d.value}</span>
        </li>
      ))}
    </ul>
  )
}

function TableBlock({ spec }: { spec: any }) {
  const rows: Record<string, unknown>[] = Array.isArray(spec.rows) ? spec.rows : []
  const columns: Column<Record<string, unknown>>[] = (spec.columns ?? []).map((c: any) => ({
    id: c.id,
    header: c.header ?? c.id,
    accessor: c.accessor ?? c.id,
    sortable: c.sortable ?? true,
    align: c.align,
  }))
  const idKey = spec.idKey ?? columns[0]?.id ?? "id"
  return (
    <div className="my-3 overflow-x-auto rounded-lg border bg-card/50">
      <DataTable
        columns={columns}
        rows={rows}
        getRowId={(r) => String(r[idKey] ?? Math.random())}
        density="compact"
      />
    </div>
  )
}

type CardSpec =
  | { kind: "pill"; status: string; label?: string }
  | { kind: "stat"; label: string; value: string | number; tone?: string }
  | { kind: "callout"; title?: string; tone?: "info" | "warning" | "danger" | "success"; body?: string }
  | { kind: string; [k: string]: unknown }

function CardBlock({ spec }: { spec: CardSpec }) {
  switch (spec.kind) {
    case "pill": {
      const s = spec as { kind: "pill"; status: string; label?: string }
      const tone =
        s.status === "active"
          ? "border-emerald-500/40 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300"
          : s.status === "suspended"
            ? "border-amber-500/40 bg-amber-500/15 text-amber-700 dark:text-amber-300"
            : s.status === "deactivated"
              ? "border-rose-500/40 bg-rose-500/15 text-rose-700 dark:text-rose-300"
              : "border-border bg-muted text-muted-foreground"
      return (
        <span className={`my-1 inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] font-medium ${tone}`}>
          {s.label ?? s.status}
        </span>
      )
    }
    case "stat": {
      const s = spec as { kind: "stat"; label: string; value: string | number }
      return (
        <span className="my-1 inline-flex items-baseline gap-1.5 rounded-md border bg-card px-2 py-1 text-sm">
          <span className="text-xs text-muted-foreground">{s.label}</span>
          <span className="font-semibold tabular-nums">{s.value}</span>
        </span>
      )
    }
    case "callout": {
      const s = spec as { kind: "callout"; title?: string; tone?: "info" | "warning" | "danger" | "success"; body?: string }
      const tone = s.tone ?? "info"
      const palette: Record<string, string> = {
        info: "border-sky-500/40 bg-sky-500/10",
        warning: "border-amber-500/40 bg-amber-500/10",
        danger: "border-rose-500/40 bg-rose-500/10",
        success: "border-emerald-500/40 bg-emerald-500/10",
      }
      return (
        <div className={`my-2 rounded-md border px-3 py-2 text-sm ${palette[tone]}`}>
          {s.title && <div className="mb-1 font-medium">{s.title}</div>}
          {s.body && <div className="text-muted-foreground">{s.body}</div>}
        </div>
      )
    }
    default:
      return (
        <pre className="my-2 rounded-md border border-border/60 bg-muted/40 p-2 text-[11px] font-mono text-muted-foreground">
          {JSON.stringify(spec, null, 2)}
        </pre>
      )
  }
}

const PROSE_COMPONENTS = {
  p: ({ children }: any) => <p className="my-1.5 leading-relaxed">{children}</p>,
  code: ({ children, className }: any) => {
    const isBlock = className?.startsWith("language-")
    if (isBlock) {
      return (
        <pre className="my-2 overflow-x-auto rounded-md bg-muted p-3 text-xs">
          <code className="font-mono">{children}</code>
        </pre>
      )
    }
    return <code className="rounded bg-muted px-1 py-0.5 font-mono text-[0.85em]">{children}</code>
  },
  ul: ({ children }: any) => <ul className="my-1.5 list-disc pl-5">{children}</ul>,
  ol: ({ children }: any) => <ol className="my-1.5 list-decimal pl-5">{children}</ol>,
  li: ({ children }: any) => <li className="my-0.5">{children}</li>,
  a: ({ children, href }: any) => (
    <a href={href} className="text-primary underline underline-offset-2" target="_blank" rel="noreferrer">
      {children}
    </a>
  ),
  table: ({ children }: any) => (
    <div className="my-2 overflow-x-auto rounded-md border">
      <table className="w-full text-sm">{children}</table>
    </div>
  ),
  thead: ({ children }: any) => <thead className="bg-muted/50 text-xs text-muted-foreground">{children}</thead>,
  th: ({ children }: any) => <th className="px-3 py-2 text-left font-medium">{children}</th>,
  td: ({ children }: any) => <td className="border-t px-3 py-2">{children}</td>,
  input: ({ checked, type, ...rest }: any) =>
    type === "checkbox" ? (
      <input type="checkbox" checked={!!checked} readOnly {...rest} className="mr-1.5 align-middle" />
    ) : (
      <input type={type} {...rest} />
    ),
}

function ProseChunk({ text }: { text: string }) {
  const trimmed = text.trim()
  if (!trimmed) return null
  return (
    <ReactMarkdown remarkPlugins={[remarkGfm]} components={PROSE_COMPONENTS}>
      {trimmed}
    </ReactMarkdown>
  )
}

export function MessageBody({ content, isToolResult, toolCalls }: MessageBodyProps) {
  const { segments, actionCount } = useMemo(() => {
    const blocks = extractActionBlocks(content)
    const cleaned = stripToolCallTags(content).replace(ACTION_BLOCK_RE, "")
    return {
      segments: parseSegments(cleaned),
      actionCount: blocks.length,
    }
  }, [content])

  if (isToolResult) {
    return (
      <div className="rounded-md border border-border/60 bg-muted/40 px-3 py-2 text-xs">
        <div className="mb-1 flex items-center gap-1.5 font-medium text-muted-foreground">
          <Wrench className="size-3" />
          Tool result
        </div>
        <pre className="overflow-x-auto whitespace-pre-wrap font-mono text-[11px] leading-snug text-foreground/80">
          {content}
        </pre>
      </div>
    )
  }

  return (
    <div className="prose prose-sm max-w-none dark:prose-invert">
      {segments.map((seg, i) =>
        seg.type === "prose" ? <ProseChunk key={i} text={seg.text} /> : renderBlock(seg.kind, seg.spec, i),
      )}
      {actionCount > 0 && (
        <span
          className="mt-1 inline-flex items-center gap-1.5 rounded-full border border-primary/40 bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary dark:border-sky-400/60 dark:bg-sky-400/15 dark:text-sky-200"
          title="Action block executed by the command bus"
        >
          <Sparkles className="size-3" />
          Ran {actionCount} action{actionCount > 1 ? "s" : ""}
        </span>
      )}
      {toolCalls && toolCalls.length > 0 && (
        <span
          className="mt-1 ml-1 inline-flex items-center gap-1.5 rounded-full border border-amber-400/40 bg-amber-400/10 px-2.5 py-0.5 text-xs font-medium text-amber-700 dark:border-amber-300/60 dark:bg-amber-300/10 dark:text-amber-200"
          title="Tool call dispatched"
        >
          <Wrench className="size-3" />
          Called {toolCalls.map((c) => c.name).join(", ")}
        </span>
      )}
    </div>
  )
}
