"use client"

import * as React from "react"

import type { ComponentDocSpec } from "@/lib/design-system/component-doc-types"
import { LeoThreadMessages } from "@/components/leo-thread-messages"
import type { LeoThreadMessage } from "@/lib/use-leo-thread"
import { Button } from "@/components/ui/button"
import { ButtonSegmentedControl } from "@/components/ui/button-segmented-control"
import {
  MessageScroller,
  MessageScrollerButton,
  MessageScrollerContent,
  MessageScrollerItem,
  MessageScrollerProvider,
  MessageScrollerViewport,
  useMessageScroller,
  useMessageScrollerScrollable,
  useMessageScrollerVisibility,
  type MessageScrollerDefaultScrollPosition,
} from "@/components/ui/message-scroller"
import { Bubble, BubbleContent } from "@/components/ui/bubble"
import { Message, MessageContent } from "@/components/ui/message"
import {
  Marker,
  MarkerContent,
  MarkerIcon,
} from "@/components/ui/marker"
import {
  HoverCard,
  HoverCardContent,
  HoverCardTrigger,
} from "@/components/ui/hover-card"
import { LeoIcon } from "@/components/ui/leo-icon"
import { cn } from "@/lib/utils"

function ex(
  section: Omit<ComponentDocSpec["sections"][number], "children" | "description">,
  children: React.ReactNode,
  description?: string,
) {
  return { ...section, description, children }
}

const SAMPLE_MESSAGES: LeoThreadMessage[] = [
  { id: "1", role: "user", content: "Which students are at risk this week?" },
  {
    id: "2",
    role: "assistant",
    content:
      "Three students have overdue compliance items before placement start. Open the Library hub filtered to non-compliant to review.",
  },
]

const LONG_THREAD: LeoThreadMessage[] = [
  {
    id: "t1",
    role: "user",
    content: "Summarize placement readiness for the spring cohort.",
  },
  {
    id: "t2",
    role: "assistant",
    content:
      "Twelve students are green. Four need immunization updates. Two contracts are still unsigned at partner sites.",
  },
  {
    id: "t3",
    role: "user",
    content: "Who needs immunization updates?",
  },
  {
    id: "t4",
    role: "assistant",
    content:
      "Avery Chen, Jordan Blake, Sam Rivera, and Priya Nair. Prioritize Avery and Jordan — their start dates are within 14 days.",
  },
  {
    id: "t5",
    role: "user",
    content: "Draft a reminder for Avery and Jordan.",
  },
  {
    id: "t6",
    role: "assistant",
    content:
      "Subject: Action needed before placement start.\n\nPlease upload your immunization records in Exxat by Friday so we can clear you for the site. Reply if you need help finding the upload form.",
  },
]

const OPENING_THREAD: LeoThreadMessage[] = [
  {
    id: "o1",
    role: "user",
    content: "This is the first message the user sent in the conversation.",
  },
  {
    id: "o2",
    role: "assistant",
    content: "Workspace creation rose 8%, but first invite completion only rose 2%.",
  },
  {
    id: "o3",
    role: "user",
    content: "This is the last message the user sent in the conversation.",
  },
  {
    id: "o4",
    role: "assistant",
    content:
      "Start with the invite step. Teams are creating workspaces but waiting to add collaborators. Compare invite drop-off by account size, then review empty-state copy on the first project screen.",
  },
]

function DemoTurn({
  message,
}: {
  message: LeoThreadMessage
}) {
  const isUser = message.role === "user"
  return (
    <Message align={isUser ? "end" : "start"}>
      <MessageContent>
        <Bubble variant={isUser ? "default" : "ghost"}>
          <BubbleContent
            className={cn(!isUser && "text-sidebar-foreground")}
          >
            {message.content}
          </BubbleContent>
        </Bubble>
      </MessageContent>
    </Message>
  )
}

function ScrollDemoShell({
  messages,
  defaultScrollPosition = "last-anchor",
  autoScroll = true,
  children,
  className,
}: {
  messages: LeoThreadMessage[]
  defaultScrollPosition?: MessageScrollerDefaultScrollPosition
  autoScroll?: boolean
  children?: React.ReactNode
  className?: string
}) {
  return (
    <MessageScrollerProvider
      key={defaultScrollPosition}
      autoScroll={autoScroll}
      defaultScrollPosition={defaultScrollPosition}
      scrollPreviousItemPeek={64}
      scrollEdgeThreshold={8}
    >
      <div className={cn("flex h-[320px] flex-col overflow-hidden rounded-lg border border-border bg-background", className)}>
        {children}
        <MessageScroller className="min-h-0 flex-1">
          <MessageScrollerViewport className="scroll-smooth px-4 pb-4 pt-3">
            <MessageScrollerContent className="mx-auto w-full min-w-0 max-w-none">
              {messages.map((message) => (
                <MessageScrollerItem
                  key={message.id}
                  messageId={message.id}
                  scrollAnchor={message.role === "user"}
                >
                  <DemoTurn message={message} />
                </MessageScrollerItem>
              ))}
            </MessageScrollerContent>
          </MessageScrollerViewport>
          <MessageScrollerButton direction="start" />
          <MessageScrollerButton direction="end" />
        </MessageScroller>
      </div>
    </MessageScrollerProvider>
  )
}

function OpeningPositionDemo() {
  const [position, setPosition] =
    React.useState<MessageScrollerDefaultScrollPosition>("last-anchor")
  return (
    // Controls live inside the transcript chrome (header strip), never over
    // the message list — floating toggles were painting on top of bubbles.
    <ScrollDemoShell
      messages={OPENING_THREAD}
      defaultScrollPosition={position}
      autoScroll={false}
      className="w-full max-w-lg"
    >
      <div className="flex shrink-0 flex-col gap-1.5 border-b border-border bg-background px-3 py-2">
        <p className="text-xs font-medium text-muted-foreground">
          defaultScrollPosition
        </p>
        <ButtonSegmentedControl
          aria-label="Opening scroll position"
          value={position}
          onValueChange={(next) =>
            setPosition(next as MessageScrollerDefaultScrollPosition)
          }
          options={[
            { value: "start", label: "Start" },
            { value: "end", label: "End" },
            { value: "last-anchor", label: "Last anchor" },
          ]}
        />
      </div>
    </ScrollDemoShell>
  )
}

function JumpCommandsBar() {
  const { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller()
  return (
    <div className="flex flex-wrap gap-1.5 border-b border-border px-3 py-2">
      <Button size="sm" variant="outline" onClick={() => scrollToStart({ behavior: "smooth" })}>
        Start
      </Button>
      <Button
        size="sm"
        variant="outline"
        onClick={() => scrollToMessage("t3", { align: "start", behavior: "smooth" })}
      >
        Jump to t3
      </Button>
      <Button
        size="sm"
        variant="outline"
        onClick={() => scrollToMessage("t5", { align: "center", behavior: "smooth" })}
      >
        Jump to t5 (center)
      </Button>
      <Button size="sm" variant="outline" onClick={() => scrollToEnd({ behavior: "smooth" })}>
        End
      </Button>
    </div>
  )
}

function JumpToMessageDemo() {
  return (
    <ScrollDemoShell messages={LONG_THREAD} defaultScrollPosition="start" autoScroll={false}>
      <JumpCommandsBar />
    </ScrollDemoShell>
  )
}

const VISIBILITY_THREAD: LeoThreadMessage[] = [
  {
    id: "vis-brief",
    role: "user",
    content: "Review the incident handoff and tell me what to read first.",
  },
  {
    id: "vis-brief-reply",
    role: "assistant",
    content:
      "Start with the summary and the impact section. The regression affected the upload queue, but the recovery path completed for every queued job.",
  },
  {
    id: "vis-impact",
    role: "user",
    content: "What was the customer impact?",
  },
  {
    id: "vis-impact-reply",
    role: "assistant",
    content:
      "Impact was limited to delayed processing.\n\nNo records were dropped, and the reconciliation worker confirmed each retry batch. Support saw confusion from two customers, but there were no checkout or billing errors.",
  },
  {
    id: "vis-actions",
    role: "user",
    content: "What actions are open?",
  },
  {
    id: "vis-actions-reply",
    role: "assistant",
    content:
      "Keep the retry window enabled until the next deploy, then add a queue-depth alert as the long-term fix.\n\nThe alert should fire on sustained queue growth, not a single short spike.",
  },
  {
    id: "vis-checklist",
    role: "user",
    content: "Give me the follow-up checklist.",
  },
  {
    id: "vis-checklist-reply",
    role: "assistant",
    content:
      "After that, compare the queue recovery graph with the deploy timeline so the handoff shows exactly when processing returned to baseline.\n\nI would also add a short owner note beside each follow-up item. The checklist is small, but ownership keeps the retry-window decision, alert tuning, and support macro from drifting into separate follow-up conversations.",
  },
]

function trimmedAnchorLabel(content: string) {
  return content.length > 42 ? `${content.slice(0, 39)}…` : content
}

/**
 * Vertical position rail + outline jump menu (shadcn Transcript Outline).
 * Ticks track `currentAnchorId`; the HoverCard lists anchored turns via Marker.
 */
function TranscriptOutline() {
  const { scrollToMessage } = useMessageScroller()
  const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility()
  const anchors = VISIBILITY_THREAD.filter((m) => m.role === "user")
  const visibleIds = new Set(visibleMessageIds)

  return (
    <HoverCard openDelay={80} closeDelay={100}>
      <HoverCardTrigger asChild>
        <button
          type="button"
          aria-label="Open transcript outline"
          className={cn(
            "flex h-9 w-9 flex-col items-center justify-center gap-1 rounded-md",
            "transition-colors outline-none",
            "focus-visible:ring-3 focus-visible:ring-ring/50",
          )}
        >
          {anchors.map((message) => {
            const current = message.id === currentAnchorId
            const visible = visibleIds.has(message.id)
            return (
              <span
                key={message.id}
                data-current={current ? "true" : undefined}
                data-visible={visible ? "true" : undefined}
                className={cn(
                  "h-0.5 w-4 rounded-full bg-muted-foreground/40 transition-colors",
                  "data-[visible=true]:bg-muted-foreground/70",
                  "data-[current=true]:bg-foreground data-[current=true]:h-1",
                )}
              />
            )
          })}
        </button>
      </HoverCardTrigger>
      <HoverCardContent
        align="center"
        side="left"
        sideOffset={8}
        className="flex w-64 flex-col gap-1 rounded-2xl p-1 shadow-md"
      >
        {anchors.map((message) => {
          const current = message.id === currentAnchorId
          return (
            <Marker
              key={message.id}
              asChild
              className={cn(
                "rounded-xl px-2 py-1.5 transition-colors",
                "hover:bg-accent hover:text-accent-foreground",
                current && "bg-accent text-accent-foreground",
              )}
            >
              <button
                type="button"
                aria-current={current ? "location" : undefined}
                onClick={() =>
                  scrollToMessage(message.id, {
                    align: "start",
                    behavior: "smooth",
                  })
                }
              >
                <MarkerIcon>
                  <i
                    className={cn(
                      "fa-light text-xs",
                      current ? "fa-location-dot" : "fa-bookmark",
                    )}
                    aria-hidden="true"
                  />
                </MarkerIcon>
                <MarkerContent className="line-clamp-1 min-w-0 text-sm text-inherit">
                  {trimmedAnchorLabel(message.content)}
                </MarkerContent>
              </button>
            </Marker>
          )
        })}
      </HoverCardContent>
    </HoverCard>
  )
}

function VisibilityTrackingDemo() {
  return (
    <MessageScrollerProvider
      autoScroll={false}
      defaultScrollPosition="start"
      scrollMargin={12}
      scrollPreviousItemPeek={64}
      scrollEdgeThreshold={8}
    >
      <div className="relative mx-auto w-full max-w-sm pe-14">
        <div className="flex h-[360px] flex-col overflow-hidden rounded-lg border border-border bg-background">
          <MessageScroller className="min-h-0 flex-1">
            <MessageScrollerViewport className="scroll-smooth px-4 pb-4 pt-3">
              <MessageScrollerContent className="mx-auto w-full min-w-0 max-w-none">
                {VISIBILITY_THREAD.map((message) => (
                  <MessageScrollerItem
                    key={message.id}
                    messageId={message.id}
                    scrollAnchor={message.role === "user"}
                  >
                    <DemoTurn message={message} />
                  </MessageScrollerItem>
                ))}
              </MessageScrollerContent>
            </MessageScrollerViewport>
            <MessageScrollerButton direction="start" />
            <MessageScrollerButton direction="end" />
          </MessageScroller>
        </div>
        <div className="absolute end-0 top-1/2 -translate-y-1/2">
          <TranscriptOutline />
        </div>
      </div>
    </MessageScrollerProvider>
  )
}

function ScrollStatusFooter() {
  const { start, end } = useMessageScrollerScrollable()
  let label = "All messages fit in the viewport."
  if (start && end) label = "Can scroll toward start and end."
  else if (start) label = "Can scroll toward start."
  else if (end) label = "Can scroll toward end."
  return (
    <div className="border-t border-border px-3 py-2 text-center text-xs text-muted-foreground">
      {label}
    </div>
  )
}

function ScrollStatusDemo() {
  return (
    <MessageScrollerProvider
      autoScroll={false}
      defaultScrollPosition="start"
      scrollPreviousItemPeek={64}
      scrollEdgeThreshold={8}
    >
      <div className="flex h-[320px] flex-col overflow-hidden rounded-lg border border-border bg-background">
        <MessageScroller className="min-h-0 flex-1">
          <MessageScrollerViewport className="scroll-smooth px-4 pb-4 pt-3">
            <MessageScrollerContent className="mx-auto w-full min-w-0 max-w-none">
              {LONG_THREAD.map((message) => (
                <MessageScrollerItem
                  key={message.id}
                  messageId={message.id}
                  scrollAnchor={message.role === "user"}
                >
                  <DemoTurn message={message} />
                </MessageScrollerItem>
              ))}
            </MessageScrollerContent>
          </MessageScrollerViewport>
          <MessageScrollerButton direction="start" />
          <MessageScrollerButton direction="end" />
        </MessageScroller>
        <ScrollStatusFooter />
      </div>
    </MessageScrollerProvider>
  )
}

const EARLIER_HISTORY: LeoThreadMessage[] = [
  {
    id: "h0",
    role: "user",
    content: "Earlier context: export queue depth spiked after the shared retry policy change.",
  },
  {
    id: "h0b",
    role: "assistant",
    content:
      "Only the export queue worker changed. Keep rollback ready if the queue starts climbing again.",
  },
]

function LoadHistoryDemo() {
  const [messages, setMessages] = React.useState(() => LONG_THREAD.slice(0, 4))

  return (
    <div className="flex flex-col gap-3">
      <Button
        size="sm"
        variant="outline"
        className="self-start"
        onClick={() =>
          setMessages((prev) =>
            prev.some((m) => m.id === "h0")
              ? prev
              : [...EARLIER_HISTORY, ...prev],
          )
        }
      >
        Load history
      </Button>
      <ScrollDemoShell
        messages={messages}
        defaultScrollPosition="end"
        autoScroll={false}
      />
    </div>
  )
}

function ThreadMarkersDemo() {
  return (
    <MessageScrollerProvider autoScroll={false} defaultScrollPosition="start">
      <div className="h-[360px] overflow-hidden rounded-lg border border-border bg-background">
        <MessageScroller className="min-h-0 h-full">
          <MessageScrollerViewport className="scroll-smooth px-4 pb-4 pt-3">
            <MessageScrollerContent className="mx-auto w-full min-w-0 gap-4">
              <MessageScrollerItem messageId="day">
                <Marker variant="separator">
                  <MarkerContent>Today</MarkerContent>
                </Marker>
              </MessageScrollerItem>
              <MessageScrollerItem messageId="u1" scrollAnchor>
                <DemoTurn
                  message={{
                    id: "u1",
                    role: "user",
                    content: "Summarize placement readiness for the spring cohort.",
                  }}
                />
              </MessageScrollerItem>
              <MessageScrollerItem messageId="tool-1">
                <Marker variant="border">
                  <MarkerIcon>
                    <i className="fa-light fa-magnifying-glass" aria-hidden="true" />
                  </MarkerIcon>
                  <MarkerContent>Reviewed 8 related records</MarkerContent>
                </Marker>
              </MessageScrollerItem>
              <MessageScrollerItem messageId="a1">
                <DemoTurn
                  message={{
                    id: "a1",
                    role: "assistant",
                    content:
                      "Twelve students are green. Four need immunization updates.",
                  }}
                />
              </MessageScrollerItem>
              <MessageScrollerItem messageId="think">
                <Marker role="status" aria-live="polite">
                  <MarkerIcon className="size-5">
                    <LeoIcon variant="ambient" size="xs" state="working" />
                  </MarkerIcon>
                  <MarkerContent className="shimmer font-medium">
                    Leo is thinking…
                  </MarkerContent>
                </Marker>
              </MessageScrollerItem>
              <MessageScrollerItem messageId="compact">
                <Marker>
                  <MarkerContent>Conversation compacted</MarkerContent>
                </Marker>
              </MessageScrollerItem>
            </MessageScrollerContent>
          </MessageScrollerViewport>
          <MessageScrollerButton direction="start" />
          <MessageScrollerButton direction="end" />
        </MessageScroller>
      </div>
    </MessageScrollerProvider>
  )
}

export const messageComponentDoc: ComponentDocSpec = {
  slug: "message",
  summary:
    "Ask Leo transcript primitives — Message, Bubble, Marker, and MessageScroller with shadcn scroll behaviors (turn anchoring, live-edge autoScroll, open position, jump controls, visibility tracking).",
  extraImports: [
    { label: "LeoThreadMessages", path: "@/components/leo-thread-messages" },
    { label: "AskLeoSidebar", path: "@/components/ask-leo-sidebar" },
    { label: "Bubble", path: "@exxatdesignux/ui/components/bubble" },
    { label: "Marker", path: "@exxatdesignux/ui/components/marker" },
    { label: "MessageScroller", path: "@exxatdesignux/ui/components/message-scroller" },
  ],
  sections: [
    ex(
      { id: "thread", title: "Thread transcript" },
      <div className="h-[280px] rounded-lg border border-border bg-background">
        <LeoThreadMessages messages={SAMPLE_MESSAGES} ariaLabel="Leo thread preview" />
      </div>,
      "User turns align end with primary Bubble; assistant uses ghost Bubble; pending uses Marker + shimmer.",
    ),
    ex(
      { id: "markers", title: "Markers in a thread" },
      <ThreadMarkersDemo />,
      "Compose Marker rows inside MessageScrollerItem: separator for day breaks, border for tool notes, role=\"status\" + shimmer for thinking.",
    ),
    ex(
      { id: "live-edge", title: "Following the live edge" },
      <div className="h-[360px] rounded-lg border border-border bg-background">
        <LeoThreadMessages
          messages={LONG_THREAD}
          ariaLabel="Leo thread scroll demo"
        />
      </div>,
      "Scroll up to release autoScroll (circular jump-to-latest appears). User turns are scrollAnchor rows with a 64px previous-item peek. Opens at last-anchor.",
    ),
    ex(
      { id: "opening-position", title: "Opening position" },
      <OpeningPositionDemo />,
      "Toggle defaultScrollPosition: start, end, or last-anchor. last-anchor opens on the last user turn when the reply does not already fit.",
    ),
    ex(
      { id: "jump-commands", title: "Jump to messages" },
      <JumpToMessageDemo />,
      "useMessageScroller drives scrollToStart, scrollToEnd, and scrollToMessage with align start | center | end | nearest.",
    ),
    ex(
      { id: "visibility", title: "Visibility tracking" },
      <VisibilityTrackingDemo />,
      "Vertical tick rail tracks currentAnchorId (and visibleMessageIds). Hover the rail to open a Marker outline and jump between anchored turns — same pattern as shadcn Transcript Outline.",
    ),
    ex(
      { id: "scroll-status", title: "Scroll status" },
      <ScrollStatusDemo />,
      "useMessageScrollerScrollable reports which edges still have content. Prefer data-scrollable on the viewport for styling.",
    ),
    ex(
      { id: "load-history", title: "Loading earlier messages" },
      <LoadHistoryDemo />,
      "preserveScrollOnPrepend keeps the reader's place when older rows load above the current transcript.",
    ),
  ],
  anatomy: [
    { part: "Message", description: "Turn row — MessageAvatar + MessageContent; align start | end." },
    { part: "Bubble", description: "variant default (user) | ghost (assistant)." },
    { part: "Marker", description: "Inline status / separator / border row — MarkerIcon + MarkerContent; role=\"status\" for thinking." },
    {
      part: "MessageScroller",
      description:
        "Provider + viewport + items + circular jump buttons. Owns turn anchoring, autoScroll, defaultScrollPosition, scrollToMessage, visibility, and edge state.",
    },
    { part: "LeoThreadMessages", description: "Product wrapper wiring avatars, bubbles, messageId, and scroller props." },
  ],
  features: [
    {
      group: "Scroll behaviors",
      icon: "fa-arrows-up-down",
      items: [
        {
          part: "Turn anchoring",
          description:
            "scrollAnchor on user turns settles the new exchange near the top with scrollPreviousItemPeek (64px) of prior context.",
        },
        {
          part: "Following the live edge",
          description:
            "autoScroll pins streamed growth only while at the bottom. Wheel, touch, keyboard, and pointer selection release follow-output.",
        },
        {
          part: "Opening position",
          description:
            "defaultScrollPosition last-anchor opens on the last meaningful turn; falls back to end when the turn fits.",
        },
        {
          part: "Jump controls",
          description:
            "Circular MessageScrollerButton start | end; scrollToMessage / scrollToEnd / scrollToStart with align, behavior, and scrollMargin.",
        },
        {
          part: "Visibility tracking",
          description:
            "useMessageScrollerVisibility drives a vertical tick rail (currentAnchorId) and a Marker jump outline (visibleMessageIds) — shadcn Transcript Outline pattern.",
        },
        {
          part: "History prepend",
          description:
            "preserveScrollOnPrepend (default true) keeps the reader's place when older rows load above.",
        },
      ],
    },
  ],
  api: [
    {
      prop: "MessageScrollerProvider.autoScroll",
      type: "boolean",
      defaultValue: "false",
      description: "Follow new content only while at the live edge. Leo threads pass true.",
    },
    {
      prop: "defaultScrollPosition",
      type: '"start" | "end" | "last-anchor"',
      defaultValue: '"end"',
      description: "First open position. Leo uses last-anchor.",
    },
    {
      prop: "scrollPreviousItemPeek",
      type: "number",
      defaultValue: "64",
      description: "Pixels of the previous item kept visible above a new scrollAnchor.",
    },
    {
      prop: "scrollEdgeThreshold",
      type: "number",
      defaultValue: "8",
      description: "Distance from an edge that still counts as start/end for buttons and state.",
    },
    {
      prop: "scrollMargin",
      type: "number",
      defaultValue: "0",
      description: "Default margin for scrollToMessage, visibility edge, and programmatic targets.",
    },
    {
      prop: "MessageScrollerViewport.preserveScrollOnPrepend",
      type: "boolean",
      defaultValue: "true",
      description: "Keep the current view when older messages are prepended.",
    },
    {
      prop: "MessageScrollerItem.messageId",
      type: "string",
      description: "Stable id for scrollToMessage and visibility tracking.",
    },
    {
      prop: "MessageScrollerItem.scrollAnchor",
      type: "boolean",
      description: "Marks the start of a turn for anchoring.",
    },
    {
      prop: "useMessageScroller()",
      type: "hooks",
      description:
        "scrollToEnd, scrollToStart, scrollToMessage (MessageScrollerScrollOptions), releaseAutoScroll, scrollable, isAtBottom.",
    },
    {
      prop: "useMessageScrollerVisibility()",
      type: "hooks",
      description:
        "{ currentAnchorId, visibleMessageIds } — pay-for-what-you-use tracking for outlines / jump menus.",
    },
    {
      prop: "useMessageScrollerScrollable()",
      type: "hooks",
      description: "{ start, end } — which edges still have content to scroll toward.",
    },
  ],
  ux: {
    job: "Render multi-turn AI chat with consistent DS tokens and scroll that never moves the reader against their intent.",
    whenToUse: [
      "Ask Leo sidebar and landing thread.",
      "Any in-product Leo Q&A transcript.",
    ],
    whenNotToUse: [
      "Chart plot insights — use ChartLeoPlotInsightOverlay.",
      "System banners or inline alerts — use Banner.",
    ],
    modernReferences: [
      "shadcn MessageScroller (turn anchor, live edge, last-anchor, visibility)",
      "ChatGPT thread layout",
      "Linear AI side panel",
    ],
    principles: ["P1", "P5", "P6", "P8"],
  },
  guidelines: {
    do: [
      "Reuse LeoThreadMessages for sidebar + landing — one transcript implementation.",
      "User bubbles: Bubble variant=\"default\"; assistant: variant=\"ghost\".",
      "Thinking state: Marker role=\"status\" + shimmer; not a toast.",
      "Pass messageId on every MessageScrollerItem; scrollAnchor on turn starts.",
      "Call releaseAutoScroll before user-driven in-place growth (expand sections).",
    ],
    dont: [
      "Do not fork scroll logic — MessageScroller owns stick-to-bottom and anchoring.",
      "Do not auto-scroll when the reader has scrolled away.",
      "Do not use Lucide — Font Awesome only per exxat-fontawesome-icons.",
    ],
  },
  relatedSlugs: ["ask-leo-button", "ask-leo-composer", "attachment", "marker"],
}
