/**
 * CollapsibleHighlight — the shared shell for every banner in the chat
 * highlight slot (todos, question, plan, approval, error, warning).
 *
 * One always-visible chip row (icon + label + optional count + close
 * button), one expandable body containing the substantive content
 * (form fields, plan markdown, todo list, etc.), and an optional
 * footer row.
 *
 * Two pieces of local state, both held per-instance:
 *  - `expanded`: clicking anywhere on the chip row (except the X)
 *    toggles the body open/closed. Initial value comes from
 *    `defaultExpanded`.
 *  - `closed`: clicking the X dismisses the whole card. When true the
 *    component returns `null`.
 *
 * Parents pass a stable `key` so React remounts on identity change
 * (a new question, a new approval batch, a new plan); both states
 * reset on remount.
 *
 * The body is rendered into the DOM unconditionally — the collapse
 * uses a grid-template-rows trick that animates height without
 * measuring, which also preserves form state, scroll position, and
 * selected tab across collapse/expand cycles.
 */

import { useState, type ReactNode } from "react";
import { cn } from "@decocms/ui/lib/utils.ts";
import { ChevronDown, X } from "@untitledui/icons";
import { useT } from "@/i18n/use-t.ts";

// ease-in-out-cubic — on-screen morph per animation guide
const EASE = "cubic-bezier(0.645, 0.045, 0.355, 1)";

export type HighlightVariant = "default" | "error" | "warning";

export interface CollapsibleHighlightProps {
  /** Small icon shown at the left of the chip row (~14px). */
  icon: ReactNode;
  /** One short phrase summarizing the banner, e.g. "Question pending". */
  label: string;
  /** Optional right-aligned count, e.g. "1 of 3" or "3 pending". */
  count?: string | null;
  /** Heading shown inside the expanded body. */
  title?: string | null;
  /** Footer-left content (e.g. <Pagination />). */
  footerLeft?: ReactNode;
  /** Footer-right content (action buttons). */
  footerRight?: ReactNode;
  /** Body content. */
  children?: ReactNode;
  /** Initial open state on first mount. */
  defaultExpanded: boolean;
  /** Drives translucent overlay for status banners. */
  variant?: HighlightVariant;
  /** Called when the close (X) button is clicked, after local dismissal. */
  onClose?: () => void;
}

/**
 * Collapsed height of a single highlight card in pixels, including the
 * `mb-2` gap below it. Equals chip row (`px-3 py-2` + `text-sm` line ≈ 36px)
 * + `mb-2` (8px) = 44px. Used by `useHighlightCount` consumers to reserve
 * exactly enough scroll padding so that when every highlight is collapsed,
 * the last message can be scrolled flush against the top of the stack.
 *
 * If the chip row's vertical padding or font size changes, update this
 * value (or measure with devtools and round up).
 */
export const HIGHLIGHT_COLLAPSED_HEIGHT_PX = 44;

// Tint is applied via an ::after pseudo-element instead of a `bg-*` utility so
// it layers over `bg-background` rather than colliding with it under twMerge
// (which would otherwise strip the solid background and leave the card
// translucent).
// `isolate` on the card creates a stacking context so `after:-z-10` keeps the
// tint behind every child (including buttons with their own backgrounds).
// Without it, the absolutely-positioned ::after layers on top of static
// siblings and bleeds red/amber through `bg-background` buttons.
const VARIANT_OVERLAY: Record<HighlightVariant, string> = {
  default: "",
  error:
    "isolate after:content-[''] after:absolute after:inset-0 after:rounded-xl after:bg-destructive/5 after:pointer-events-none after:-z-10",
  warning:
    "isolate after:content-[''] after:absolute after:inset-0 after:rounded-xl after:bg-warning/5 after:pointer-events-none after:-z-10",
};

const VARIANT_BORDER: Record<HighlightVariant, string> = {
  default: "border-border",
  error: "border-destructive/30",
  warning: "border-warning/30",
};

const VARIANT_ICON_COLOR: Record<HighlightVariant, string> = {
  default: "text-muted-foreground",
  error: "text-destructive",
  warning: "text-warning",
};

export function CollapsibleHighlight({
  icon,
  label,
  count,
  title,
  footerLeft,
  footerRight,
  children,
  defaultExpanded,
  variant = "default",
  onClose,
}: CollapsibleHighlightProps) {
  const t = useT();
  const [expanded, setExpanded] = useState(defaultExpanded);
  const [closed, setClosed] = useState(false);

  const handleClose = () => {
    setClosed(true);
    onClose?.();
  };

  if (closed) return null;

  return (
    <div
      data-testid="collapsible-highlight"
      data-variant={variant}
      className={cn(
        "relative flex flex-col rounded-xl bg-background border shadow-md",
        "w-[calc(100%-16px)] max-w-[640px] mx-auto mb-2",
        VARIANT_BORDER[variant],
        VARIANT_OVERLAY[variant],
      )}
    >
      <button
        type="button"
        data-testid="collapsible-highlight-chip"
        aria-expanded={expanded}
        onClick={() => setExpanded((prev) => !prev)}
        className={cn(
          "flex items-center gap-2 w-full px-3 py-2 text-sm text-left",
          "hover:bg-accent/50 transition-colors rounded-t-xl",
          expanded && "rounded-b-none",
          !expanded && "rounded-b-xl",
        )}
      >
        <span className={cn("shrink-0", VARIANT_ICON_COLOR[variant])}>
          {icon}
        </span>
        <span className="flex-1 min-w-0 truncate">{label}</span>
        {count ? (
          <span className="text-xs text-muted-foreground shrink-0">
            {count}
          </span>
        ) : null}
        <span
          aria-hidden="true"
          className="text-muted-foreground shrink-0 flex items-center justify-center"
        >
          <ChevronDown
            size={16}
            style={{
              transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
              transition: `transform 180ms ${EASE}`,
            }}
          />
        </span>
        {/*
          Rendered as a nested <span role="button"> rather than a real
          <button> because the outer chip is already a <button> and
          nesting interactive elements is invalid HTML. stopPropagation
          on click + keydown keeps the chip's expand/collapse from
          firing when the user dismisses the card.
        */}
        <span
          role="button"
          tabIndex={0}
          data-testid="collapsible-highlight-close"
          aria-label={t("chat.collapsibleHighlight.closeLabel")}
          title={t("chat.collapsibleHighlight.closeTitle")}
          onClick={(e) => {
            e.stopPropagation();
            handleClose();
          }}
          onKeyDown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              e.stopPropagation();
              handleClose();
            }
          }}
          className="text-muted-foreground hover:text-foreground transition-colors shrink-0 flex items-center justify-center -mr-1 ml-1 p-1 rounded-md hover:bg-accent"
        >
          <X size={16} />
        </span>
      </button>

      {/* Collapsible body — grid trick animates height without measuring */}
      <div
        data-testid="collapsible-highlight-body"
        style={{
          display: "grid",
          gridTemplateRows: expanded ? "1fr" : "0fr",
          transition: `grid-template-rows 180ms ${EASE}`,
        }}
      >
        <div style={{ overflow: "hidden", minHeight: 0 }}>
          <div
            className="flex flex-col"
            style={{
              opacity: expanded ? 1 : 0,
              transition: `opacity 120ms ${EASE}`,
              // Cap the card so a long question can't grow off the top of the
              // screen (it floats above the composer). Title + options scroll;
              // the footer stays pinned so Skip/Next are always reachable.
              maxHeight: "60vh",
            }}
          >
            <div className="flex-1 min-h-0 overflow-y-auto">
              {title ? (
                <div className="flex items-start gap-2 px-4 pt-4 pb-5 border-t border-dashed border-border/60">
                  <p className="flex-1 text-base font-medium text-foreground min-w-0">
                    {title}
                  </p>
                </div>
              ) : (
                <div className="border-t border-dashed border-border/60" />
              )}

              {/* When a title is present, the title block's `pt-4` already
                  provides the 16px gap from the dashed divider to the first
                  body element. When no title is present (e.g. TodosHighlight),
                  the children must carry that top padding themselves so every
                  card has the same chip → body distance. */}
              {children ? (
                <div className={cn("pb-4", !title && "pt-4")}>{children}</div>
              ) : null}
            </div>

            {footerLeft || footerRight ? (
              <div className="shrink-0 border-t border-border px-3 py-3 pb-6">
                <div className="flex items-center justify-between">
                  <div>{footerLeft}</div>
                  <div className="flex items-center gap-2">{footerRight}</div>
                </div>
              </div>
            ) : null}
          </div>
        </div>
      </div>
    </div>
  );
}
