/**
 * Button Component Generator
 *
 * Generates a unified Button ComponentSet in Figma that matches
 * the Button component props exactly:
 *
 * - variant: primary, secondary, ghost, destructive, secondary-destructive, outline
 * - size: xs, sm, base, lg
 * - shape: base, square, circle
 * - disabled: true, false
 * - loading: true, false
 * - state: default, hover, focus, pressed
 *
 * Reads all definitions from component-registry.json (the source of truth).
 * Uses real icons from the Icon Library page.
 *
 * @see packages/kumo/src/components/button/button.tsx
 */

import {
  createTextNode,
  bindFillToVariable,
  bindStrokeToVariable,
  getVariableByName,
  createModeSection,
  createRowLabel,
  createColumnHeaders,
  setWhiteTextColor,
  bindTextColorToVariable,
  BORDER_RADIUS,
  SECTION_PADDING,
  SECTION_GAP,
  GRID_LAYOUT,
  FALLBACK_VALUES,
  SECTION_LAYOUT,
  SECTION_TITLE,
  OPACITY,
  VAR_NAMES,
} from "./shared";
import { parseTailwindClasses } from "../parsers/tailwind-to-figma";
import {
  getButtonIcon,
  createLoader,
  bindIconColor,
  DEFAULT_ICONS,
} from "./icon-utils";
import { logInfo, logWarn } from "../logger";

// Import variant data from the registry (generated by build:ai-metadata)
import registry from "@cloudflare/kumo/ai/component-registry.json";

// Extract props from registry
const buttonProps = registry.components.Button.props;

const variantProp = buttonProps.variant as {
  values: string[];
  classes: Record<string, string>;
  descriptions: Record<string, string>;
  default: string;
};

const sizeProp = buttonProps.size as {
  values: string[];
  classes: Record<string, string>;
  descriptions: Record<string, string>;
  default: string;
};

const shapeProp = buttonProps.shape as {
  values: string[];
  classes: Record<string, string>;
  descriptions: Record<string, string>;
  default: string;
};

/**
 * TESTABLE EXPORTS - Pure functions that return intermediate data
 * These functions compute data without calling Figma APIs, enabling snapshot tests.
 */

/**
 * Get variant configuration from registry
 */
export function getButtonVariantConfig() {
  return {
    values: variantProp.values,
    classes: variantProp.classes,
    descriptions: variantProp.descriptions,
    default: variantProp.default,
  };
}

/**
 * Get size configuration from registry
 */
export function getButtonSizeConfig() {
  return {
    values: sizeProp.values,
    classes: sizeProp.classes,
    descriptions: sizeProp.descriptions,
    default: sizeProp.default,
  };
}

/**
 * Get shape configuration from registry
 */
export function getButtonShapeConfig() {
  return {
    values: shapeProp.values,
    classes: shapeProp.classes,
    descriptions: shapeProp.descriptions,
    default: shapeProp.default,
  };
}

/**
 * Get parsed base styles
 */
export function getButtonParsedBaseStyles() {
  const baseStyles = "flex items-center font-medium";
  return parseTailwindClasses(baseStyles);
}

/**
 * Get parsed styles for a specific variant
 */
export function getButtonParsedVariantStyles(variant: string) {
  const classes = variantProp.classes[variant] || "";
  return {
    variant,
    classes,
    description: variantProp.descriptions[variant] || "",
    parsed: parseTailwindClasses(classes),
  };
}

/**
 * Get parsed styles for a specific size
 */
export function getButtonParsedSizeStyles(size: string) {
  const classes = sizeProp.classes[size] || "";
  return {
    size,
    classes,
    description: sizeProp.descriptions[size] || "",
    parsed: parseTailwindClasses(classes),
  };
}

/**
 * Get parsed styles for a specific shape
 */
export function getButtonParsedShapeStyles(shape: string) {
  const classes = shapeProp.classes[shape] || "";
  return {
    shape,
    classes,
    description: shapeProp.descriptions[shape] || "",
    parsed: parseTailwindClasses(classes),
  };
}

/**
 * Compact size mapping from button.tsx KUMO_BUTTON_VARIANTS.compactSize
 * Used for square and circle shapes.
 * Parses size-* Tailwind classes to derive pixel values.
 *
 * Source: button.tsx KUMO_BUTTON_VARIANTS.compactSize
 * - xs: size-3.5 = 14px
 * - sm: size-6.5 = 26px
 * - base: size-9 = 36px
 * - lg: size-10 = 40px
 *
 * Note: compactSize is not yet exported to component-registry.json,
 * so we use the class strings directly from the React component.
 */
export function getCompactSizeMap(): Record<string, number> {
  // Compact size classes from KUMO_BUTTON_VARIANTS.compactSize in button.tsx
  const compactSizeClasses: Record<string, string> = {
    xs: "size-3.5",
    sm: "size-6.5",
    base: "size-9",
    lg: "size-10",
  };

  const result: Record<string, number> = {};

  for (const size of sizeProp.values) {
    const classes = compactSizeClasses[size];
    if (classes) {
      const parsed = parseTailwindClasses(classes);
      // Use parsed width if available, otherwise fallback to centralized constants
      // (this fallback should never trigger if parser is working correctly)
      const fallbackSize =
        FALLBACK_VALUES.buttonCompactSize[
          size as keyof typeof FALLBACK_VALUES.buttonCompactSize
        ];
      result[size] =
        parsed.width ?? fallbackSize ?? FALLBACK_VALUES.height.base;
    }
  }

  return result;
}

const COMPACT_SIZE_MAP_LOCAL: Record<string, number> = getCompactSizeMap();

/**
 * Get state styles mapping
 * Derived from hover/focus/pressed states in variant classes from registry
 */
export function getStateStylesMap(): Record<
  string,
  Record<
    string,
    {
      fillVariable?: string;
      fillOpacity?: number;
      strokeVariable?: string;
      addRing?: boolean;
      textOpacity?: number;
    }
  >
> {
  return {
    primary: {
      // hover:bg-kumo-brand-hover focus:bg-kumo-brand-hover
      hover: { fillVariable: VAR_NAMES.color.brandHover },
      focus: { fillVariable: VAR_NAMES.color.brandHover, addRing: true },
      pressed: { fillVariable: VAR_NAMES.color.brandHover },
    },
    secondary: {
      // not-disabled:hover:bg-secondary, not-disabled:hover:border-secondary!
      hover: {
        fillVariable: VAR_NAMES.color.control,
        strokeVariable: VAR_NAMES.color.control,
      },
      focus: { addRing: true },
      // data-[state=open]:bg-kumo-control
      pressed: { fillVariable: VAR_NAMES.color.control },
    },
    ghost: {
      // hover:bg-kumo-tint
      hover: { fillVariable: VAR_NAMES.color.tint },
      focus: { addRing: true },
      pressed: { fillVariable: VAR_NAMES.color.tint },
    },
    destructive: {
      // hover:bg-kumo-danger/70
      hover: { fillVariable: VAR_NAMES.color.danger + "/70" },
      focus: { addRing: true },
      pressed: { fillVariable: VAR_NAMES.color.danger + "/70" },
    },
    "secondary-destructive": {
      // not-disabled:hover:bg-secondary, not-disabled:hover:border-secondary!
      hover: {
        fillVariable: VAR_NAMES.color.control,
        strokeVariable: VAR_NAMES.color.control,
      },
      focus: { addRing: true },
      pressed: { fillVariable: VAR_NAMES.color.control },
    },
    outline: {
      hover: { fillVariable: VAR_NAMES.color.control },
      focus: { addRing: true },
      pressed: { fillVariable: VAR_NAMES.color.control },
    },
  };
}

/**
 * State-specific style overrides
 * Maps variant × state to specific Figma styling
 *
 * These are derived from the stateClasses in component-registry.json:
 * - hover: hover:bg-* classes
 * - focus: ring-kumo-hairline (focus-visible ring)
 * - pressed: same as hover but slightly darker
 *
 * Note: Figma doesn't support pseudo-states, so we create separate
 * component variants for each state to document the visual appearance.
 */
const STATE_STYLES: Record<
  string,
  Record<
    string,
    {
      fillVariable?: string;
      fillOpacity?: number;
      strokeVariable?: string;
      addRing?: boolean;
      textOpacity?: number;
    }
  >
> = getStateStylesMap();

/**
 * Get all variant data (for snapshot testing)
 * Returns intermediate data before Figma API calls
 */
export function getAllButtonVariantData() {
  const baseStyles = getButtonParsedBaseStyles();
  const variantConfig = getButtonVariantConfig();
  const sizeConfig = getButtonSizeConfig();
  const shapeConfig = getButtonShapeConfig();
  const compactSizeMap = getCompactSizeMap();
  const stateStyles = getStateStylesMap();

  return {
    baseStyles: {
      raw: "flex items-center font-medium",
      parsed: baseStyles,
    },
    variants: variantConfig.values.map((variant) => {
      const variantData = getButtonParsedVariantStyles(variant);
      return {
        ...variantData,
        stateStyles: stateStyles[variant] || {},
      };
    }),
    sizes: sizeConfig.values.map((size) => {
      const sizeData = getButtonParsedSizeStyles(size);
      return {
        ...sizeData,
        compactSize: compactSizeMap[size],
      };
    }),
    shapes: shapeConfig.values.map((shape) => {
      return getButtonParsedShapeStyles(shape);
    }),
    compactSizeMap,
    stateStyles,
  };
}

/**
 * Create a single button component
 */
async function createButtonComponent(
  variant: string,
  size: string,
  shape: string,
  disabled: boolean,
  loading: boolean,
  state: string,
): Promise<ComponentNode> {
  const variantClasses = variantProp.classes[variant] || "";
  const sizeClasses = sizeProp.classes[size] || "";

  // Parse styles from Tailwind classes
  const variantStyles = parseTailwindClasses(variantClasses);
  const sizeStyles = parseTailwindClasses(sizeClasses);

  // Create component
  const component = figma.createComponent();
  component.name =
    "variant=" +
    variant +
    ", size=" +
    size +
    ", shape=" +
    shape +
    ", disabled=" +
    disabled +
    ", loading=" +
    loading +
    ", state=" +
    state;

  const isCompactShape = shape === "square" || shape === "circle";

  // Configure auto-layout
  component.layoutMode = "HORIZONTAL";
  component.primaryAxisAlignItems = "CENTER";
  component.counterAxisAlignItems = "CENTER";

  if (isCompactShape) {
    // Square/circle: fixed size, no padding
    const buttonSize =
      COMPACT_SIZE_MAP_LOCAL[size] || FALLBACK_VALUES.height.base;
    component.primaryAxisSizingMode = "FIXED";
    component.counterAxisSizingMode = "FIXED";
    component.resize(buttonSize, buttonSize);
    component.paddingLeft = 0;
    component.paddingRight = 0;
  } else {
    // Base shape: hug contents with padding
    component.primaryAxisSizingMode = "AUTO";
    component.counterAxisSizingMode = "FIXED";
    component.paddingLeft =
      sizeStyles.paddingX || FALLBACK_VALUES.padding.horizontal;
    component.paddingRight =
      sizeStyles.paddingX || FALLBACK_VALUES.padding.horizontal;
    component.resize(100, sizeStyles.height || FALLBACK_VALUES.height.base);
  }

  component.paddingTop = 0;
  component.paddingBottom = 0;
  component.itemSpacing = sizeStyles.gap || FALLBACK_VALUES.gap.standard;

  // Set corner radius based on shape
  if (shape === "circle") {
    component.cornerRadius = BORDER_RADIUS.full;
  } else {
    // Both base and square use size-based radius
    component.cornerRadius =
      sizeStyles.borderRadius !== undefined
        ? sizeStyles.borderRadius
        : BORDER_RADIUS.lg;
  }

  // Apply fill from variant
  if (variantStyles.fillVariable) {
    const fillVar = getVariableByName(variantStyles.fillVariable);
    if (fillVar) {
      bindFillToVariable(component, fillVar.id);
    }
  } else {
    // Transparent background (ghost)
    component.fills = [];
  }

  // Apply stroke for variants with borders (ring or border)
  if (variantStyles.hasBorder && variantStyles.strokeVariable) {
    const strokeVar = getVariableByName(variantStyles.strokeVariable);
    if (strokeVar) {
      bindStrokeToVariable(component, strokeVar.id, 1);
    }
  }

  // Apply state-specific styles (hover, focus, pressed)
  if (state !== "default" && !disabled && !loading) {
    const stateStyle = STATE_STYLES[variant] && STATE_STYLES[variant][state];
    if (stateStyle) {
      // Apply state-specific fill
      if (stateStyle.fillVariable) {
        // Try the exact variable name first (e.g., "color-primary/70")
        const stateFillVar = getVariableByName(stateStyle.fillVariable);
        if (stateFillVar) {
          bindFillToVariable(component, stateFillVar.id);
        } else {
          // Fallback: try without opacity suffix and apply opacity separately
          const baseVarName = stateStyle.fillVariable.split("/")[0];
          const opacityMatch = stateStyle.fillVariable.match(/\/(\d+)$/);
          const baseFillVar = getVariableByName(baseVarName);
          if (baseFillVar) {
            bindFillToVariable(component, baseFillVar.id);
            if (opacityMatch) {
              // Apply opacity to the fill
              const opacityValue = parseInt(opacityMatch[1], 10) / 100;
              const fills = component.fills;
              if (fills && fills.length > 0) {
                const newFills = [];
                for (let fi = 0; fi < fills.length; fi++) {
                  const fill = Object.assign({}, fills[fi]);
                  fill.opacity = opacityValue;
                  newFills.push(fill);
                }
                component.fills = newFills;
              }
            }
          } else {
            // Both exact and base variable missing - use fallback
            logWarn(
              "State fill variable not found:",
              stateStyle.fillVariable,
              "for variant=" + variant,
              "state=" + state + ". Using default fill.",
            );
            // Apply a default fill color (using color-surface as fallback)
            const fallbackVar = getVariableByName(VAR_NAMES.color.base);
            if (fallbackVar) {
              bindFillToVariable(component, fallbackVar.id);
            }
          }
        }
      }

      // Apply state-specific stroke
      if (stateStyle.strokeVariable) {
        const stateStrokeVar = getVariableByName(stateStyle.strokeVariable);
        if (stateStrokeVar) {
          bindStrokeToVariable(component, stateStrokeVar.id, 1);
        }
      }

      // Add focus ring (ring-kumo-hairline)
      if (stateStyle.addRing) {
        const ringVar = getVariableByName(VAR_NAMES.color.brand);
        if (ringVar) {
          bindStrokeToVariable(component, ringVar.id, 2);
        }
      }
    }
  }

  // Apply disabled state: opacity-50
  if (disabled) {
    component.opacity = OPACITY.disabled;
  }

  // Add loader for loading state
  if (loading) {
    const loaderSize = size === "lg" ? 16 : 14;
    const loader = createLoader(loaderSize);
    component.appendChild(loader);
  }

  // Add icon for compact shapes (when not loading)
  if (isCompactShape && !loading) {
    const icon = getButtonIcon(DEFAULT_ICONS.plus, size);

    // Bind icon color based on variant
    if (variantStyles.isWhiteText) {
      bindIconColor(icon, "text-white");
    } else if (variantStyles.textVariable) {
      bindIconColor(icon, variantStyles.textVariable);
    } else {
      bindIconColor(icon, "text-kumo-default");
    }

    component.appendChild(icon);
  }

  // Add text label for base shape
  if (shape === "base") {
    const fontWeight = FALLBACK_VALUES.fontWeight.medium; // font-medium
    const labelText = loading ? "Loading..." : "Button";
    const textNode = await createTextNode(
      labelText,
      sizeStyles.fontSize || FALLBACK_VALUES.fontSize,
      fontWeight,
    );
    textNode.name = "Label";

    // Apply text color from variant
    if (variantStyles.isWhiteText) {
      setWhiteTextColor(textNode);
    } else if (variantStyles.textVariable) {
      const textVar = getVariableByName(variantStyles.textVariable);
      if (textVar) {
        bindTextColorToVariable(textNode, textVar.id);
      }
    }

    component.appendChild(textNode);
  }

  return component;
}

/**
 * Generate Button ComponentSet with state documentation
 *
 * Layout strategy - organized by rows:
 *
 * Row 1-6 (variants): Each variant shows all states + disabled + loading
 *   Columns: default | hover | focus | pressed | disabled | loading
 *
 * Row 7 (square): Secondary variant, all sizes
 *   Columns: xs | sm | base | lg
 *
 * Row 8 (circle): Secondary variant, all sizes
 *   Columns: xs | sm | base | lg
 *
 * This gives a clear view of:
 * - How each variant looks in different states
 * - The disabled and loading states for each variant
 * - Shape variations with size options
 */

/**
 * Generate Button ComponentSet with reduced variants
 *
 * Creates a single "Button" ComponentSet with smart variant selection
 * that covers all use cases without redundancy. Then creates instances
 * in both light and dark mode sections.
 *
 * @param page - Target page for components
 * @param startY - Y position to start placing sections
 * @returns The Y position after all sections
 */
export async function generateButtonComponents(
  page: PageNode,
  startY: number,
): Promise<number> {
  if (startY === undefined) startY = 100;

  figma.currentPage = page;

  const variants = variantProp.values;
  const sizes = sizeProp.values;

  // Generate selected combinations only
  const components: ComponentNode[] = [];

  // Track row labels: { y, text }
  const rowLabels: { y: number; text: string }[] = [];

  // Layout grid spacing
  const componentGap = 16;
  const rowGap = GRID_LAYOUT.rowGap.extraSpacious;
  const headerRowHeight = GRID_LAYOUT.headerRowHeight;
  const labelColumnWidth = GRID_LAYOUT.labelColumnWidth.wider;

  // Track layout position by row type
  // - Rows 0-5: text buttons by variant (base shape)
  // - Row 6: square shape buttons
  // - Row 7: circle shape buttons
  const rowComponents: Map<number, ComponentNode[]> = new Map();

  // Track row labels by index
  const rowLabelTexts: Map<number, string> = new Map();

  // Track column headers: { x, text }
  let columnHeaders: { x: number; text: string }[] = [];

  // Generate components in a specific order for clean layout
  // For base shape: states first, then disabled, then loading
  const baseShapeColumnOrder = [
    { state: "default", disabled: false, loading: false },
    { state: "hover", disabled: false, loading: false },
    { state: "focus", disabled: false, loading: false },
    { state: "pressed", disabled: false, loading: false },
    { state: "default", disabled: true, loading: false },
    { state: "default", disabled: false, loading: true },
  ];

  // Generate base shape buttons (all variants × states/disabled/loading)
  for (let vi = 0; vi < variants.length; vi++) {
    const variant = variants[vi];
    let rowIndex = vi;
    rowLabelTexts.set(rowIndex, "variant=" + variant);

    if (!rowComponents.has(rowIndex)) {
      rowComponents.set(rowIndex, []);
    }

    for (let ci = 0; ci < baseShapeColumnOrder.length; ci++) {
      const colConfig = baseShapeColumnOrder[ci];
      const component = await createButtonComponent(
        variant,
        "base", // Always base size for variant rows
        "base", // Always base shape
        colConfig.disabled,
        colConfig.loading,
        colConfig.state,
      );

      rowComponents.get(rowIndex)!.push(component);
      components.push(component);
    }
  }

  // Generate base shape buttons showing all sizes (text buttons)
  const sizeRowIndex = variants.length;
  rowLabelTexts.set(sizeRowIndex, "shape=base (sizes)");
  if (!rowComponents.has(sizeRowIndex)) {
    rowComponents.set(sizeRowIndex, []);
  }

  for (let sizeIdx = 0; sizeIdx < sizes.length; sizeIdx++) {
    const sizeVal = sizes[sizeIdx];
    const sizeComponent = await createButtonComponent(
      "secondary",
      sizeVal,
      "base", // base shape = text button
      false,
      false,
      "default",
    );
    rowComponents.get(sizeRowIndex)!.push(sizeComponent);
    components.push(sizeComponent);
  }

  // Generate square shape buttons (secondary variant × all sizes)
  const squareRowIndex = variants.length + 1;
  rowLabelTexts.set(squareRowIndex, "shape=square");
  if (!rowComponents.has(squareRowIndex)) {
    rowComponents.set(squareRowIndex, []);
  }

  for (let si = 0; si < sizes.length; si++) {
    const size = sizes[si];
    const squareComponent = await createButtonComponent(
      "secondary",
      size,
      "square",
      false,
      false,
      "default",
    );
    rowComponents.get(squareRowIndex)!.push(squareComponent);
    components.push(squareComponent);
  }

  // Generate circle shape buttons (secondary variant × all sizes)
  const circleRowIndex = variants.length + 2;
  rowLabelTexts.set(circleRowIndex, "shape=circle");
  if (!rowComponents.has(circleRowIndex)) {
    rowComponents.set(circleRowIndex, []);
  }

  for (let si2 = 0; si2 < sizes.length; si2++) {
    const size2 = sizes[si2];
    const circleComponent = await createButtonComponent(
      "secondary",
      size2,
      "circle",
      false,
      false,
      "default",
    );
    rowComponents.get(circleRowIndex)!.push(circleComponent);
    components.push(circleComponent);
  }

  // Define column headers for variant rows (states + disabled + loading)
  const variantColumnHeaders = [
    "default",
    "hover",
    "focus",
    "pressed",
    "disabled",
    "loading",
  ];

  // Define column headers for shape rows (sizes)
  const shapeColumnHeaders = ["xs", "sm", "base", "lg"];

  // Track shape section column headers separately
  const shapeColumnHeaderPositions: { x: number; text: string }[] = [];

  // Track column X positions for shape section (set from first shape row - text buttons)
  const shapeColumnXPositions: number[] = [];

  // Layout components in rows
  let yOffset = headerRowHeight; // Start below header row
  const variantRowCount = variants.length;
  const totalRows = variantRowCount + 3; // variants + base sizes + square + circle

  // Gap between variant section and shape section (for shape column headers)
  const sectionGap = 60; // Extra space for shape section header

  // First shape row index (base sizes with text)
  const firstShapeRowIndex = variantRowCount;

  for (let i = 0; i < totalRows; i++) {
    const row = rowComponents.get(i) || [];
    let xOffset = labelColumnWidth;

    // Add extra gap before shape rows for shape column headers
    if (i === variantRowCount) {
      yOffset += sectionGap;
    }

    // Record row label position
    const labelText = rowLabelTexts.get(i);
    if (labelText && row.length > 0) {
      rowLabels.push({ y: yOffset, text: labelText });
    }

    // Record column headers from first row
    if (i === 0) {
      for (let hi = 0; hi < variantColumnHeaders.length; hi++) {
        columnHeaders.push({
          x: xOffset + hi * (100 + componentGap), // Approximate width
          text: variantColumnHeaders[hi],
        });
      }
    }

    // Check if this is a shape row (after variant rows)
    const isShapeRow = i >= firstShapeRowIndex;
    const isFirstShapeRow = i === firstShapeRowIndex;

    for (let j = 0; j < row.length; j++) {
      const comp = row[j];

      // For shape rows after the first one, use stored column positions
      if (isShapeRow && !isFirstShapeRow && j < shapeColumnXPositions.length) {
        comp.x = shapeColumnXPositions[j];
      } else {
        comp.x = xOffset;
      }
      comp.y = yOffset;

      // Record column positions and headers from first shape row (text buttons)
      if (isFirstShapeRow && j < shapeColumnHeaders.length) {
        shapeColumnXPositions.push(xOffset);
        shapeColumnHeaderPositions.push({
          x: xOffset,
          text: shapeColumnHeaders[j],
        });
      }

      xOffset += comp.width + componentGap;
    }

    if (row.length > 0) {
      yOffset += rowGap;
    }
  }

  // Update column headers with actual positions from first row
  const firstRow = rowComponents.get(0) || [];
  if (firstRow.length > 0) {
    columnHeaders = [];
    let headerXOffset = labelColumnWidth;
    for (let chi = 0; chi < firstRow.length; chi++) {
      const headerComp = firstRow[chi];
      columnHeaders.push({
        x: headerXOffset,
        text: variantColumnHeaders[chi] || "",
      });
      headerXOffset += headerComp.width + componentGap;
    }
  }

  // Calculate shape section header Y position (just above first shape row)
  let shapeHeaderY = 0;
  const squareRow = rowComponents.get(variantRowCount) || [];
  if (squareRow.length > 0) {
    shapeHeaderY = squareRow[0].y - headerRowHeight - 8; // 8px gap above row
  }

  // Combine all into a single ComponentSet
  const componentSet = figma.combineAsVariants(components, page);
  componentSet.name = "Button";
  componentSet.description =
    "Button component with variant, size, shape, disabled, loading, and state properties";

  componentSet.layoutMode = "NONE";

  // Calculate content dimensions (add label column width and header row)
  const contentWidth = componentSet.width + labelColumnWidth;
  const contentHeight = componentSet.height + headerRowHeight;

  // Content Y offset to make room for title inside frame
  const contentYOffset = SECTION_TITLE.height;

  // Create light mode section
  const lightSection = createModeSection(page, "Button", "light");
  lightSection.frame.resize(
    contentWidth + SECTION_PADDING * 2,
    contentHeight + SECTION_PADDING * 2 + contentYOffset,
  );

  // Create dark mode section
  const darkSection = createModeSection(page, "Button", "dark");
  darkSection.frame.resize(
    contentWidth + SECTION_PADDING * 2,
    contentHeight + SECTION_PADDING * 2 + contentYOffset,
  );

  // Add title inside each frame

  // Move ComponentSet into light section frame
  lightSection.frame.appendChild(componentSet);
  componentSet.x = SECTION_PADDING + labelColumnWidth;
  componentSet.y = SECTION_PADDING + headerRowHeight + contentYOffset;

  // Add column headers to light section (variant state headers)
  await createColumnHeaders(
    columnHeaders.map(function (h) {
      return { x: h.x + SECTION_PADDING, text: h.text };
    }),
    SECTION_PADDING + contentYOffset,
    lightSection.frame,
  );

  // Add shape column headers to light section (size headers for shape rows)
  if (shapeColumnHeaderPositions.length > 0 && shapeHeaderY > 0) {
    await createColumnHeaders(
      shapeColumnHeaderPositions.map(function (h) {
        return { x: h.x + SECTION_PADDING, text: h.text };
      }),
      SECTION_PADDING + contentYOffset + shapeHeaderY,
      lightSection.frame,
    );
  }

  // Add row labels to light section
  for (let li = 0; li < rowLabels.length; li++) {
    const label = rowLabels[li];
    const labelNode = await createRowLabel(
      label.text,
      SECTION_PADDING,
      SECTION_PADDING +
        contentYOffset +
        label.y +
        GRID_LAYOUT.labelVerticalOffset.lg, // Large offset to vertically center with button
    );
    lightSection.frame.appendChild(labelNode);
  }

  // Create instances of all components for dark section
  // Note: component positions are relative to ComponentSet after combineAsVariants
  // We need to add labelColumnWidth to match the light section layout
  for (let k = 0; k < components.length; k++) {
    const origComp = components[k];
    const instance = origComp.createInstance();
    instance.x = origComp.x + SECTION_PADDING + labelColumnWidth;
    instance.y =
      origComp.y + SECTION_PADDING + headerRowHeight + contentYOffset;
    darkSection.frame.appendChild(instance);
  }

  // Add column headers to dark section (variant state headers)
  await createColumnHeaders(
    columnHeaders.map(function (h) {
      return { x: h.x + SECTION_PADDING, text: h.text };
    }),
    SECTION_PADDING + contentYOffset,
    darkSection.frame,
  );

  // Add shape column headers to dark section (size headers for shape rows)
  if (shapeColumnHeaderPositions.length > 0 && shapeHeaderY > 0) {
    await createColumnHeaders(
      shapeColumnHeaderPositions.map(function (h) {
        return { x: h.x + SECTION_PADDING, text: h.text };
      }),
      SECTION_PADDING + contentYOffset + shapeHeaderY,
      darkSection.frame,
    );
  }

  // Add row labels to dark section
  for (let di = 0; di < rowLabels.length; di++) {
    const darkLabel = rowLabels[di];
    const darkLabelNode = await createRowLabel(
      darkLabel.text,
      SECTION_PADDING,
      SECTION_PADDING +
        contentYOffset +
        darkLabel.y +
        GRID_LAYOUT.labelVerticalOffset.lg,
    );
    darkSection.frame.appendChild(darkLabelNode);
  }

  // Resize sections to fit content
  const totalWidth = contentWidth + SECTION_PADDING * 2;
  const totalHeight = contentHeight + SECTION_PADDING * 2 + contentYOffset;

  lightSection.section.resizeWithoutConstraints(totalWidth, totalHeight);
  darkSection.section.resizeWithoutConstraints(totalWidth, totalHeight);

  // Position sections at startY (no title offset needed since title is inside)
  lightSection.frame.x = SECTION_LAYOUT.startX;
  lightSection.frame.y = startY;

  darkSection.frame.x =
    lightSection.frame.x + totalWidth + SECTION_LAYOUT.modeGap; // Side by side with gap
  darkSection.frame.y = startY;

  logInfo(
    "✅ Generated Button ComponentSet with " +
      components.length +
      " variants (light + dark)",
  );

  return startY + totalHeight + SECTION_GAP;
}

/**
 * Exports for tests and backwards compatibility
 */
export const BUTTON_VARIANTS_EXPORT = variantProp.values;
export const BUTTON_SIZES_EXPORT = sizeProp.values;
export const BUTTON_SHAPES_EXPORT = shapeProp.values;
export const BUTTON_DISABLED_OPTIONS = [false, true];
export const BUTTON_LOADING_OPTIONS = [false, true];
export const BUTTON_STATE_OPTIONS = ["default", "hover", "focus", "pressed"];
