"use client";

import { memo, useEffect, useState } from "react";
import {
  Handle,
  Position,
  useReactFlow,
  useUpdateNodeInternals,
  type Node,
  type NodeProps,
} from "@xyflow/react";
import {
  Box,
  Chip,
  Divider,
  IconButton,
  Paper,
  Tooltip,
  Typography,
} from "@mui/material";
import { alpha } from "@mui/material/styles";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { useTranslation } from "react-i18next";
import ItemTracingDocLink from "./ItemTracingDocLink";
import ItemTracingLotTraceLink from "./ItemTracingLotTraceLink";
import ItemTracingStockTakeLifecycle from "./ItemTracingStockTakeLifecycle";
import { TraceFlowNodeData } from "./buildReactFlowGraph";
import { NODE_HEIGHT, NODE_WIDTH, NODE_WIDTH_BRANCH, DO_GROUP_HEADER } from "./traceFlowConstants";
import { traceFlowHandleTopPercent } from "./traceFlowEdgeLayout";
import { kindColor, kindLabelKey } from "./traceFlowNodeUtils";
import { formatQty, formatSignedQty } from "./traceQtyUtils";
import { buildStockTakeLifecycleStages } from "./traceStockTakeUtils";
import { pickStatusValueColor, resolveDoOutboundQtyColor } from "./traceLabelUtils";

const PHASE_LABEL_INNER = 96;
/** Expanded stock-take stage panel (mt + maxHeight + padding) below the fixed card. */
const STOCK_TAKE_LIFECYCLE_PANEL_EXTRA = 320;

const GroupCollapseButton = ({
  collapsed,
  onToggle,
  collapseLabel,
  expandLabel,
}: {
  collapsed: boolean;
  onToggle?: () => void;
  collapseLabel: string;
  expandLabel: string;
}) => (
  <IconButton
    size="small"
    aria-label={collapsed ? expandLabel : collapseLabel}
    aria-expanded={!collapsed}
    onClick={(e) => {
      e.stopPropagation();
      onToggle?.();
    }}
    onMouseDown={(e) => e.stopPropagation()}
    sx={{
      pointerEvents: "all",
      p: 0.25,
      ml: "auto",
      flexShrink: 0,
      color: "warning.dark",
    }}
  >
    {collapsed ? (
      <ExpandMoreIcon fontSize="small" />
    ) : (
      <ExpandLessIcon fontSize="small" />
    )}
  </IconButton>
);

/** FP-MTMS Version Checklist | Functions Ref. No. 23 | v1.0.0 | 2026-07-20 */
export const TraceFlowEventNode = memo(function TraceFlowEventNode({
  id,
  data,
}: NodeProps<Node<TraceFlowNodeData>>) {
  const { t } = useTranslation("itemTracing");
  const { setNodes } = useReactFlow();
  const updateNodeInternals = useUpdateNodeInternals();
  const node = data.layoutNode;
  const color = kindColor(node.kind);
  const isQc = node.kind === "QC" || node.kind === "MATERIAL_QC";
  const isStockTake = node.kind === "STOCK_TAKE";
  const hasLifecycle = isStockTake && node.stockTakeRecordDetail != null;
  const [lifecycleExpanded, setLifecycleExpanded] = useState(false);
  const lifecycleStages = hasLifecycle
    ? buildStockTakeLifecycleStages(node.stockTakeRecordDetail)
    : [];
  const nodeWidth = data.compact ? NODE_WIDTH_BRANCH : NODE_WIDTH;

  useEffect(() => {
    if (!hasLifecycle) return;
    const nextH = lifecycleExpanded
      ? NODE_HEIGHT + STOCK_TAKE_LIFECYCLE_PANEL_EXTRA
      : NODE_HEIGHT;
    setNodes((nds) =>
      nds.map((n) => {
        if (n.id !== id) return n;
        return {
          ...n,
          height: nextH,
          style: { ...(n.style ?? {}), width: nodeWidth, height: nextH },
          measured: {
            width: n.measured?.width ?? n.width ?? nodeWidth,
            height: nextH,
          },
        };
      }),
    );
    const raf = requestAnimationFrame(() => updateNodeInternals(id));
    return () => cancelAnimationFrame(raf);
  }, [
    hasLifecycle,
    lifecycleExpanded,
    id,
    nodeWidth,
    setNodes,
    updateNodeInternals,
  ]);

  const chipLabel =
    node.kind === "PUTAWAY" && node.putawayStatusLabel?.trim()
      ? node.putawayStatusLabel
      : isQc && node.qcTypeLabel?.trim()
        ? node.qcTypeLabel
        : t(kindLabelKey(node.kind));
  const dateLabel = node.timestamp?.trim() ? node.timestamp : t("noTimestamp");
  const isDoOut = node.kind === "DO_OUT";
  const doOutKindChipSx = {
    height: data.compact ? 18 : 20,
    "& .MuiChip-label": {
      px: 0.75,
      fontSize: data.compact ? "0.65rem" : "0.7rem",
      fontWeight: 600,
    },
  } as const;
  const stopCardClick = (e: React.MouseEvent) => {
    e.stopPropagation();
  };
  const doOutboundKindChips =
    isDoOut && (node.doOutboundIsExtra || node.doOutboundIsReplenish) ? (
      <>
        {node.doOutboundIsExtra ? (
          <Chip
            size="small"
            label={t("doOutboundExtra")}
            color="secondary"
            sx={doOutKindChipSx}
            onClick={stopCardClick}
            onMouseDown={stopCardClick}
          />
        ) : null}
        {node.doOutboundIsReplenish ? (
          <Chip
            size="small"
            label={t("doOutboundReplenish")}
            color="success"
            sx={doOutKindChipSx}
            onClick={stopCardClick}
            onMouseDown={stopCardClick}
          />
        ) : null}
      </>
    ) : null;
  const titleContent = isDoOut ? (
    <Box
      component="span"
      sx={{
        display: "inline-flex",
        flexWrap: "wrap",
        alignItems: "center",
        gap: 0.5,
        maxWidth: "100%",
      }}
    >
      {node.docLinkKind && node.refCode ? (
        <ItemTracingDocLink
          kind={node.docLinkKind}
          code={node.refCode}
          id={node.refId}
          consoCode={node.consoCode ?? node.refCode}
          ticketNo={node.docLinkTicketNo}
          targetDate={node.docLinkTargetDate}
          openInNewTab
        />
      ) : (
        node.refCode || "—"
      )}
    </Box>
  ) : node.docLinkKind && node.refCode ? (
    <ItemTracingDocLink
      kind={node.docLinkKind}
      code={node.refCode}
      id={node.refId}
      consoCode={node.consoCode ?? node.refCode}
      ticketNo={node.docLinkTicketNo}
      targetDate={node.docLinkTargetDate}
      openInNewTab
    />
  ) : (
    node.title
  );
  const incomingCount = data.incomingHandleCount ?? 0;
  const outgoingCount = data.outgoingHandleCount ?? 0;
  const searchActive = data.searchActive ?? false;
  const searchMatch = data.searchMatch ?? false;
  const searchFocused = data.searchFocused ?? false;
  const nodeSelected = data.nodeSelected ?? false;

  const hiddenHandleStyle = { opacity: 0, width: 8, height: 8, transform: "none" as const };

  const activeCount = lifecycleStages.filter((s) => s.isActive).length;

  const card = (
    <Paper
      variant="outlined"
      sx={{
        width: nodeWidth,
        minWidth: nodeWidth,
        maxWidth: nodeWidth,
        height: NODE_HEIGHT,
        display: "flex",
        flexDirection: "column",
        overflow: "hidden",
        position: "relative",
        borderColor: searchFocused
          ? "primary.main"
          : nodeSelected
            ? "primary.main"
            : searchMatch
              ? "warning.main"
              : isQc
                ? `${color}.main`
                : undefined,
        borderWidth: searchFocused || searchMatch || nodeSelected ? 2 : isQc ? 2 : 1,
        opacity: searchActive && !searchMatch ? 0.35 : 1,
        transition: "opacity 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease",
        boxShadow: searchFocused ? 8 : nodeSelected ? 6 : searchMatch ? 4 : undefined,
        cursor: "pointer",
        "&:hover": { boxShadow: searchFocused || searchMatch || nodeSelected ? undefined : 6 },
      }}
    >
      {incomingCount > 0 &&
        Array.from({ length: incomingCount }, (_, i) => (
          <Handle
            key={`in-${i}`}
            id={`in-${i}`}
            type="target"
            position={Position.Left}
            style={{
              ...hiddenHandleStyle,
              top: traceFlowHandleTopPercent(i, incomingCount),
              left: 0,
            }}
          />
        ))}
      <Box sx={{ height: 4, bgcolor: `${color}.main`, flexShrink: 0 }} />
      <Box
        sx={{
          p: data.compact ? 1 : 1.25,
          pb: hasLifecycle ? (data.compact ? 0.5 : 0.75) : undefined,
          flex: 1,
          minHeight: 0,
          overflow: "hidden",
        }}
      >
        <Box
          sx={{
            display: "flex",
            alignItems: "center",
            flexWrap: "wrap",
            gap: 0.5,
            mb: 0.75,
          }}
        >
          {isQc && (
            <Box
              sx={{
                width: 8,
                height: 8,
                bgcolor: `${color}.main`,
                transform: "rotate(45deg)",
                flexShrink: 0,
              }}
            />
          )}
          <Chip size="small" label={chipLabel} color={color} />
          {doOutboundKindChips}
        </Box>
        <Box
          sx={{
            lineHeight: 1.3,
            fontSize: data.compact ? "0.8rem" : undefined,
            fontWeight: 700,
            overflow: "hidden",
            overflowWrap: "anywhere",
            wordBreak: "break-word",
            ...(isDoOut
              ? { display: "flex", flexWrap: "wrap", alignItems: "center", gap: 0.5 }
              : {
                  display: "-webkit-box",
                  WebkitLineClamp: 2,
                  WebkitBoxOrient: "vertical",
                }),
          }}
          title={typeof node.refCode === "string" && node.refCode.trim() ? node.refCode : node.title}
        >
          {titleContent}
        </Box>
        {node.subtitle?.trim() ? (
          <Typography
            variant="caption"
            color="text.secondary"
            display="block"
            title={node.subtitle}
            sx={{
              mt: 0.5,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
            }}
          >
            {node.subtitle}
          </Typography>
        ) : null}
        {node.kind === "MATERIAL_PICK" && node.traceItemCode?.trim() ? (
          <Typography
            variant="body2"
            fontWeight={600}
            title={node.meta?.trim() || node.traceItemCode}
            sx={{
              mt: 0.5,
              fontSize: data.compact ? "0.8rem" : undefined,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
            }}
          >
            {t("Item")}: {node.meta?.trim() || node.traceItemCode}
          </Typography>
        ) : null}
        {(node.qty != null || node.kind === "STOCK_TAKE") && (
          <Typography
            variant="body2"
            fontWeight={600}
            title={
              node.kind === "ADJUSTMENT"
                ? formatSignedQty(node.qty, node.adjustmentDirection, node.uom)
                : formatQty(node.qty, node.uom)
            }
            sx={{
              mt: 0.5,
              fontSize: data.compact ? "0.8rem" : undefined,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
              color:
                node.kind === "DO_OUT" ||
                node.kind === "JO_OUT" ||
                node.kind === "MATERIAL_PICK"
                  ? resolveDoOutboundQtyColor(
                      node.qty,
                      node.kind === "DO_OUT" ? node.doOutboundQtyChanged : false,
                    )
                  : undefined,
            }}
          >
            {node.kind === "PURCHASE"
              ? t("detailOrderQty")
              : node.kind === "STOCK_TAKE"
                ? t("after")
                : node.kind === "ADJUSTMENT"
                  ? t("variance")
                  : node.kind === "QC" || node.kind === "MATERIAL_QC" || node.kind === "FAIL"
                    ? t("unqualifiedQty")
                    : node.kind === "PRODUCTION_STEP"
                      ? t("processOutputQty")
                      : t("qty")}
            :{" "}
            {node.kind === "ADJUSTMENT"
              ? formatSignedQty(node.qty, node.adjustmentDirection, node.uom)
              : formatQty(node.qty, node.uom)}
          </Typography>
        )}
        {node.kind === "STOCK_TAKE" ? (
          <Typography
            variant="body2"
            fontWeight={600}
            title={formatQty(node.stockTakeVarianceQty, node.uom)}
            sx={{
              mt: 0.25,
              fontSize: data.compact ? "0.8rem" : undefined,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
              color:
                node.stockTakeVarianceQty != null && node.stockTakeVarianceQty > 0
                  ? "success.main"
                  : node.stockTakeVarianceQty != null && node.stockTakeVarianceQty < 0
                    ? "error.main"
                    : undefined,
            }}
          >
            {t("variance")}: {formatQty(node.stockTakeVarianceQty, node.uom)}
          </Typography>
        ) : null}
        {node.traceLotNo ? (
          <ItemTracingLotTraceLink
            label={
              node.kind === "BYPRODUCT"
                ? t("traceByproductLot")
                : node.kind === "REPACK"
                  ? t("traceRepackLot")
                  : t("traceMaterialLot")
            }
            lotNo={node.traceLotNo}
            itemCode={node.traceItemCode}
            stopPropagation
            sx={{ mt: 0.5, display: "inline-block" }}
          />
        ) : null}
        {node.warehouseCode?.trim() ? (
          <Typography
            variant="caption"
            color="text.secondary"
            display="block"
            title={node.warehouseCode}
            sx={{
              mt: 0.5,
              fontWeight: 600,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 2,
              WebkitBoxOrient: "vertical",
            }}
          >
            {t("warehouse")}: {node.warehouseCode}
          </Typography>
        ) : null}
        {(node.kind === "JO_OUT" || node.kind === "MATERIAL_PICK") &&
        node.processingStatusLabel?.trim() ? (
          <Typography
            variant="caption"
            color="text.secondary"
            display="block"
            title={node.processingStatusLabel}
            sx={{
              mt: 0.5,
              fontWeight: 600,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 1,
              WebkitBoxOrient: "vertical",
            }}
          >
            {t("processingStatus")}:{" "}
            <Box
              component="span"
              sx={{
                color: (() => {
                  const c = pickStatusValueColor(node.processingStatus);
                  return c === "default" ? "text.primary" : `${c}.main`;
                })(),
                fontWeight: 700,
              }}
            >
              {node.processingStatusLabel}
            </Box>
          </Typography>
        ) : null}
        {(node.kind === "JO_OUT" || node.kind === "MATERIAL_PICK") &&
        node.matchStatusLabel?.trim() ? (
          <Typography
            variant="caption"
            color="text.secondary"
            display="block"
            title={node.matchStatusLabel}
            sx={{
              mt: 0.5,
              fontWeight: 600,
              overflow: "hidden",
              overflowWrap: "anywhere",
              wordBreak: "break-word",
              display: "-webkit-box",
              WebkitLineClamp: 1,
              WebkitBoxOrient: "vertical",
            }}
          >
            {t("matchStatus")}:{" "}
            <Box
              component="span"
              sx={{
                color: (() => {
                  const c = pickStatusValueColor(node.matchStatus);
                  return c === "default" ? "text.primary" : `${c}.main`;
                })(),
                fontWeight: 700,
              }}
            >
              {node.matchStatusLabel}
            </Box>
          </Typography>
        ) : null}
        <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.75 }}>
          {dateLabel}
        </Typography>
      </Box>
      {hasLifecycle ? (
        <Box
          sx={{
            flexShrink: 0,
            borderTop: 1,
            borderColor: "divider",
            px: data.compact ? 0.75 : 1,
            py: 0.25,
          }}
        >
          <Box
            role="button"
            tabIndex={0}
            aria-expanded={lifecycleExpanded}
            aria-label={lifecycleExpanded ? t("stockTakeStageCollapse") : t("stockTakeStageExpand")}
            onClick={(e) => {
              e.stopPropagation();
              setLifecycleExpanded((v) => !v);
            }}
            onKeyDown={(e) => {
              if (e.key === "Enter" || e.key === " ") {
                e.preventDefault();
                e.stopPropagation();
                setLifecycleExpanded((v) => !v);
              }
            }}
            sx={{
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: 0.5,
              width: "100%",
              py: 0.25,
              px: 0.5,
              borderRadius: 1,
              cursor: "pointer",
              userSelect: "none",
              "&:hover": { bgcolor: "action.hover" },
            }}
          >
            <Typography variant="caption" fontWeight={700} color="secondary.main" noWrap>
              {t("nodeStockTake")} · {t("stockTakeStageDetail", { count: activeCount })}
            </Typography>
            {lifecycleExpanded ? (
              <ExpandLessIcon fontSize="small" color="secondary" />
            ) : (
              <ExpandMoreIcon fontSize="small" color="secondary" />
            )}
          </Box>
        </Box>
      ) : null}
      {outgoingCount > 0 &&
        Array.from({ length: outgoingCount }, (_, i) => (
          <Handle
            key={`out-${i}`}
            id={`out-${i}`}
            type="source"
            position={Position.Right}
            style={{
              ...hiddenHandleStyle,
              top: traceFlowHandleTopPercent(i, outgoingCount),
              right: 0,
            }}
          />
        ))}
    </Paper>
  );

  // Inline under the card so pan/zoom keeps the panel attached (Popper portals to body).
  const lifecyclePanel =
    hasLifecycle && lifecycleExpanded ? (
      <Paper
        variant="outlined"
        className="nowheel nodrag nopan"
        onClick={(e) => e.stopPropagation()}
        onMouseDown={(e) => e.stopPropagation()}
        onWheel={(e) => e.stopPropagation()}
        sx={{
          width: nodeWidth,
          maxWidth: nodeWidth,
          mt: 0.75,
          p: 1.25,
          boxShadow: 4,
          borderColor: "secondary.main",
          borderWidth: 1.5,
          maxHeight: STOCK_TAKE_LIFECYCLE_PANEL_EXTRA - 24,
          overflow: "auto",
          overscrollBehavior: "contain",
          bgcolor: "background.paper",
          pointerEvents: "all",
        }}
      >
        <Typography variant="caption" fontWeight={700} color="secondary.main" sx={{ mb: 1, display: "block" }}>
          {t("nodeStockTake")} · {t("stockTakeStageDetail", { count: activeCount })}
        </Typography>
        <Divider sx={{ mb: 1 }} />
        <ItemTracingStockTakeLifecycle stages={lifecycleStages} uom={node.uom} />
      </Paper>
    ) : null;

  const wrapped = (
    <Box
      sx={{
        width: nodeWidth,
        height: lifecycleExpanded
          ? NODE_HEIGHT + STOCK_TAKE_LIFECYCLE_PANEL_EXTRA
          : NODE_HEIGHT,
        overflow: "visible",
      }}
    >
      {card}
      {lifecyclePanel}
    </Box>
  );

  if (node.meta) {
    return (
      <Tooltip title={node.meta} arrow placement="top">
        {wrapped}
      </Tooltip>
    );
  }
  return wrapped;
});

export const TraceFlowPhaseLabelNode = memo(function TraceFlowPhaseLabelNode({
  data,
}: NodeProps<Node<TraceFlowNodeData>>) {
  return (
    <Box
      sx={{
        width: PHASE_LABEL_INNER,
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        px: 0.75,
        py: 1,
        borderLeft: `4px solid ${data.phaseColor ?? "#757575"}`,
        backgroundImage: `linear-gradient(${data.phaseColor ?? "#757575"}14, ${data.phaseColor ?? "#757575"}14)`,
        borderRadius: 1,
      }}
    >
      <Typography
        variant="caption"
        fontWeight={700}
        sx={{
          writingMode: "vertical-rl",
          textOrientation: "mixed",
          color: data.phaseColor ?? "#757575",
          letterSpacing: 0.5,
        }}
      >
        {data.phaseLabel}
      </Typography>
    </Box>
  );
});

export const TraceFlowDateHeaderNode = memo(function TraceFlowDateHeaderNode({
  data,
}: NodeProps<Node<TraceFlowNodeData>>) {
  return (
    <Typography variant="caption" color="text.secondary" fontWeight={600} sx={{ width: 80, textAlign: "center" }}>
      {data.dateLabel}
    </Typography>
  );
});

export const TraceFlowDoGroupNode = memo(function TraceFlowDoGroupNode({
  data,
}: NodeProps<Node<TraceFlowNodeData>>) {
  const { t } = useTranslation("itemTracing");
  const node = data.layoutNode;
  const incomingCount = data.incomingHandleCount ?? 0;
  const searchActive = data.searchActive ?? false;
  const searchMatch = data.searchMatch ?? false;
  const searchFocused = data.searchFocused ?? false;
  const collapsed = data.groupCollapsed === true;
  const width = node.groupBoxWidth ?? NODE_WIDTH;
  const height = collapsed ? DO_GROUP_HEADER : (node.groupBoxHeight ?? NODE_HEIGHT);
  const hiddenHandleStyle = { opacity: 0, width: 8, height: 8, transform: "none" as const };

  return (
    <Box
      sx={{
        width,
        height,
        border: 2,
        borderStyle: "dashed",
        borderColor: searchFocused
          ? "primary.main"
          : searchMatch
            ? "warning.main"
            : "warning.light",
        borderRadius: 1,
        bgcolor: (theme) => alpha(theme.palette.background.paper, 0.45),
        opacity: searchActive && !searchMatch ? 0.35 : 1,
        boxShadow: searchFocused ? 4 : searchMatch ? 2 : undefined,
        position: "relative",
        overflow: "hidden",
        pointerEvents: "none",
      }}
    >
      {incomingCount > 0 &&
        Array.from({ length: incomingCount }, (_, i) => (
          <Handle
            key={`in-${i}`}
            id={`in-${i}`}
            type="target"
            position={Position.Left}
            style={{
              ...hiddenHandleStyle,
              top: traceFlowHandleTopPercent(i, incomingCount),
              left: 0,
              pointerEvents: "all",
            }}
          />
        ))}
      <Box
        sx={{
          height: DO_GROUP_HEADER,
          px: 1.25,
          display: "flex",
          alignItems: "center",
          gap: 1,
          overflow: "hidden",
          bgcolor: (theme) => alpha(theme.palette.warning.light, 0.35),
          borderBottom: collapsed ? 0 : 1,
          borderColor: "warning.light",
        }}
      >
        <Typography variant="caption" fontWeight={700} noWrap sx={{ color: "warning.dark", minWidth: 0 }}>
          {node.title}
        </Typography>
        {node.groupTotalQty != null ? (
          <Typography variant="caption" fontWeight={600} noWrap sx={{ color: "warning.dark", flexShrink: 0 }}>
            {t("flowDoGroupTotalQty", {
              qtyLabel: formatQty(node.groupTotalQty, node.groupUom),
            })}
          </Typography>
        ) : null}
        <GroupCollapseButton
          collapsed={collapsed}
          onToggle={data.onToggleGroupCollapse}
          collapseLabel={t("flowGroupCollapse")}
          expandLabel={t("flowGroupExpand")}
        />
      </Box>
    </Box>
  );
});

export const TraceFlowPickGroupNode = memo(function TraceFlowPickGroupNode({
  data,
}: NodeProps<Node<TraceFlowNodeData>>) {
  const { t } = useTranslation("itemTracing");
  const node = data.layoutNode;
  const incomingCount = data.incomingHandleCount ?? 0;
  const outgoingCount = data.outgoingHandleCount ?? 0;
  const searchActive = data.searchActive ?? false;
  const searchMatch = data.searchMatch ?? false;
  const searchFocused = data.searchFocused ?? false;
  const collapsed = data.groupCollapsed === true;
  const width = node.groupBoxWidth ?? NODE_WIDTH;
  const height = collapsed ? DO_GROUP_HEADER : (node.groupBoxHeight ?? NODE_HEIGHT);
  const hiddenHandleStyle = { opacity: 0, width: 8, height: 8, transform: "none" as const };

  return (
    <Box
      sx={{
        width,
        height,
        border: 2,
        borderStyle: "dashed",
        borderColor: searchFocused
          ? "primary.main"
          : searchMatch
            ? "warning.main"
            : "warning.light",
        borderRadius: 1,
        bgcolor: (theme) => alpha(theme.palette.background.paper, 0.45),
        opacity: searchActive && !searchMatch ? 0.35 : 1,
        boxShadow: searchFocused ? 4 : searchMatch ? 2 : undefined,
        position: "relative",
        overflow: "hidden",
        pointerEvents: "none",
      }}
    >
      {incomingCount > 0 &&
        Array.from({ length: incomingCount }, (_, i) => (
          <Handle
            key={`in-${i}`}
            id={`in-${i}`}
            type="target"
            position={Position.Left}
            style={{
              ...hiddenHandleStyle,
              top: traceFlowHandleTopPercent(i, incomingCount),
              left: 0,
              pointerEvents: "all",
            }}
          />
        ))}
      <Box
        sx={{
          height: DO_GROUP_HEADER,
          px: 1.25,
          display: "flex",
          alignItems: "center",
          gap: 0.5,
          bgcolor: (theme) => alpha(theme.palette.warning.light, 0.35),
          borderBottom: collapsed ? 0 : 1,
          borderColor: "warning.light",
        }}
      >
        <Typography variant="caption" fontWeight={700} noWrap sx={{ flex: 1, color: "warning.dark", minWidth: 0 }}>
          {node.title}
        </Typography>
        <GroupCollapseButton
          collapsed={collapsed}
          onToggle={data.onToggleGroupCollapse}
          collapseLabel={t("flowGroupCollapse")}
          expandLabel={t("flowGroupExpand")}
        />
      </Box>
      {outgoingCount > 0 &&
        Array.from({ length: outgoingCount }, (_, i) => (
          <Handle
            key={`out-${i}`}
            id={`out-${i}`}
            type="source"
            position={Position.Right}
            style={{
              ...hiddenHandleStyle,
              top: traceFlowHandleTopPercent(i, outgoingCount),
              right: 0,
              pointerEvents: "all",
            }}
          />
        ))}
    </Box>
  );
});

export const traceFlowNodeTypes = {
  traceEvent: TraceFlowEventNode,
  doGroup: TraceFlowDoGroupNode,
  pickGroup: TraceFlowPickGroupNode,
  phaseLabel: TraceFlowPhaseLabelNode,
  dateHeader: TraceFlowDateHeaderNode,
};
