{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "schema-builder",
  "title": "Schema Builder",
  "description": "A JSON schema builder with form and JSON views for extraction schemas.",
  "dependencies": [
    "@dnd-kit/core@^6.3.1",
    "@dnd-kit/sortable@^10.0.0",
    "@dnd-kit/utilities@^3.2.2",
    "@pierre/diffs@^1.2.7",
    "next-themes@0.4.6",
    "@base-ui/react@^1.4.1"
  ],
  "registryDependencies": [
    "utils",
    "button",
    "collapsible",
    "dropdown-menu",
    "scroll-area",
    "tabs"
  ],
  "files": [
    {
      "path": "components/extend/schema-builder.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\"\nimport {\n  closestCenter,\n  DndContext,\n  DragOverlay,\n  getFirstCollision,\n  KeyboardSensor,\n  MeasuringStrategy,\n  PointerSensor,\n  pointerWithin,\n  rectIntersection,\n  useDroppable,\n  useSensor,\n  useSensors,\n  type CollisionDetection,\n  type DragEndEvent,\n  type DragOverEvent,\n  type DragStartEvent,\n  type UniqueIdentifier,\n} from \"@dnd-kit/core\"\nimport {\n  arrayMove,\n  SortableContext,\n  sortableKeyboardCoordinates,\n  useSortable,\n  verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\"\nimport { CSS } from \"@dnd-kit/utilities\"\nimport { Virtualizer as DiffsVirtualizer } from \"@pierre/diffs\"\nimport {\n  File,\n  VirtualizerContext,\n  WorkerPoolContextProvider,\n  type VirtualFileMetrics,\n  type WorkerInitializationRenderOptions,\n  type WorkerPoolOptions,\n} from \"@pierre/diffs/react\"\nimport { useTheme } from \"next-themes\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Collapsible,\n  CollapsibleContent as CollapsiblePanel,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport {\n  ScrollArea as InlineScrollArea,\n  ScrollBar,\n} from \"@/components/ui/scroll-area\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { IconPlaceholder } from \"@/components/icon-placeholder\"\n\nfunction CancelCircleGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"CircleX\"\n      tabler=\"IconCircleX\"\n      hugeicons=\"CancelCircleIcon\"\n      phosphor=\"XCircleIcon\"\n      remixicon=\"RiCloseCircleLine\"\n      {...props}\n    />\n  )\n}\nfunction InputNumericGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Hash\"\n      tabler=\"IconNumber123\"\n      hugeicons=\"InputNumericIcon\"\n      phosphor=\"NumpadIcon\"\n      remixicon=\"RiHashtag\"\n      {...props}\n    />\n  )\n}\nfunction InputTextGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"TextCursorInput\"\n      tabler=\"IconForms\"\n      hugeicons=\"InputTextIcon\"\n      phosphor=\"TextboxIcon\"\n      remixicon=\"RiInputField\"\n      {...props}\n    />\n  )\n}\nfunction LeftToRightListBulletGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Columns3Icon\"\n      tabler=\"IconLayoutColumns\"\n      hugeicons=\"LeftToRightListBulletIcon\"\n      phosphor=\"ColumnsIcon\"\n      remixicon=\"RiLayoutColumnLine\"\n      {...props}\n    />\n  )\n}\nfunction SecondBracketGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Brackets\"\n      tabler=\"IconBrackets\"\n      hugeicons=\"SecondBracketIcon\"\n      phosphor=\"BracketsSquareIcon\"\n      remixicon=\"RiBracketsLine\"\n      {...props}\n    />\n  )\n}\nfunction SourceCodeSquareGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SquareCode\"\n      tabler=\"IconSourceCode\"\n      hugeicons=\"SourceCodeSquareIcon\"\n      phosphor=\"CodeIcon\"\n      remixicon=\"RiCodeBlock\"\n      {...props}\n    />\n  )\n}\nfunction TextCheckGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"CaptionsIcon\"\n      tabler=\"IconTextCaption\"\n      hugeicons=\"TextCheckIcon\"\n      phosphor=\"TextTIcon\"\n      remixicon=\"RiTextWrap\"\n      {...props}\n    />\n  )\n}\nexport type SchemaBuilderScalarType =\n  | \"string\"\n  | \"number\"\n  | \"integer\"\n  | \"boolean\"\n  | \"null\"\nexport type SchemaBuilderFieldType =\n  | SchemaBuilderScalarType\n  | \"object\"\n  | \"array\"\n  | \"enum\"\nexport type SchemaBuilderArrayScalarType = Exclude<\n  SchemaBuilderScalarType,\n  \"null\"\n>\nexport type SchemaBuilderArrayItemType =\n  | SchemaBuilderArrayScalarType\n  | \"object\"\n  | \"enum\"\nexport type SchemaBuilderEnumValue = {\n  id: string\n  value: string\n  description: string\n}\nexport type SchemaBuilderProperty = {\n  id: string\n  key: string\n  type: SchemaBuilderFieldType\n  description: string\n  enumValues?: SchemaBuilderEnumValue[]\n  properties?: SchemaBuilderProperty[]\n  items?: {\n    type: SchemaBuilderArrayItemType\n    properties?: SchemaBuilderProperty[]\n    enumValues?: SchemaBuilderEnumValue[]\n  }\n}\nexport type SchemaBuilderSchema = {\n  properties: SchemaBuilderProperty[]\n}\nexport type SchemaBuilderTheme = \"light\" | \"dark\"\nexport type SerializedSchemaProperty = {\n  type: string\n  description?: string\n  enum?: string[]\n  enumDescriptions?: Record<string, string>\n  properties?: Record<string, SerializedSchemaProperty>\n  items?: SerializedSchemaProperty\n}\nexport type SerializedSchema = {\n  type: \"object\"\n  properties: Record<string, SerializedSchemaProperty>\n}\nconst SCALAR_TYPES: SchemaBuilderScalarType[] = [\n  \"string\",\n  \"number\",\n  \"integer\",\n  \"boolean\",\n  \"null\",\n]\nconst ARRAY_SCALAR_TYPES: SchemaBuilderArrayScalarType[] = [\n  \"string\",\n  \"number\",\n  \"integer\",\n  \"boolean\",\n]\nconst ROOT_SCHEMA_CONTAINER_ID = \"schema-root\"\nconst SCHEMA_PROPERTY_DRAG_TYPE = \"schema-property\"\nconst ENUM_VALUE_DRAG_TYPE = \"enum-value\"\nconst SCHEMA_PROPERTY_BLOCK_ATTRIBUTE = \"data-schema-builder-property-id\"\nconst SCHEMA_PROPERTY_ROW_ATTRIBUTE = \"data-schema-builder-property-row-id\"\nconst PROPERTY_REORDER_EDGE_THRESHOLD_PX = 18\nconst SCHEMA_DND_MEASURING = {\n  droppable: {\n    strategy: MeasuringStrategy.WhileDragging,\n  },\n}\ntype SchemaCollisionArgs = Parameters<CollisionDetection>[0]\nfunction getDroppableRectArea(\n  droppableRects: SchemaCollisionArgs[\"droppableRects\"],\n  id: UniqueIdentifier\n) {\n  const rect = droppableRects.get(id)\n  return rect ? rect.width * rect.height : Number.POSITIVE_INFINITY\n}\nfunction getProjectedPropertyChildContainerId(\n  properties: SchemaBuilderProperty[],\n  propertyId: UniqueIdentifier,\n  args: SchemaCollisionArgs\n) {\n  const overLocation = findPropertyLocation(properties, String(propertyId))\n  const childContainerId = overLocation\n    ? getPropertyChildContainerId(overLocation.property)\n    : null\n  const overRect = args.droppableRects.get(propertyId)\n  if (\n    childContainerId &&\n    args.pointerCoordinates &&\n    overRect &&\n    args.pointerCoordinates.x > overRect.left + overRect.width * 0.18\n  ) {\n    return childContainerId\n  }\n  return null\n}\nfunction getSchemaPropertyElementRect(attribute: string, id: UniqueIdentifier) {\n  if (typeof document === \"undefined\") return null\n  for (const element of document.querySelectorAll<HTMLElement>(\n    `[${attribute}]`\n  )) {\n    if (element.getAttribute(attribute) === String(id)) {\n      return element.getBoundingClientRect()\n    }\n  }\n  return null\n}\nfunction getPropertyBlockRect(id: UniqueIdentifier, args: SchemaCollisionArgs) {\n  return (\n    getSchemaPropertyElementRect(SCHEMA_PROPERTY_BLOCK_ATTRIBUTE, id) ??\n    args.droppableRects.get(id) ??\n    null\n  )\n}\nfunction getPropertyRowRect(id: UniqueIdentifier) {\n  return getSchemaPropertyElementRect(SCHEMA_PROPERTY_ROW_ATTRIBUTE, id)\n}\nconst TYPE_LABELS: Record<\n  SchemaBuilderFieldType | `array-${SchemaBuilderArrayItemType}`,\n  string\n> = {\n  string: \"String\",\n  number: \"Number\",\n  integer: \"Integer\",\n  boolean: \"Boolean\",\n  null: \"Null\",\n  object: \"Object\",\n  array: \"Array\",\n  enum: \"Enum\",\n  \"array-string\": \"Array<string>\",\n  \"array-number\": \"Array<number>\",\n  \"array-integer\": \"Array<integer>\",\n  \"array-boolean\": \"Array<boolean>\",\n  \"array-enum\": \"Array<enum>\",\n  \"array-object\": \"Array<object>\",\n}\ntype SchemaBuilderTypeStyleKey =\n  | SchemaBuilderFieldType\n  | `array-${SchemaBuilderArrayItemType}`\nconst TYPE_STYLES: Record<\n  SchemaBuilderTypeStyleKey,\n  {\n    icon: React.ComponentType<InlineRegistryIconProps>\n    badge: string\n  }\n> = {\n  string: {\n    icon: InputTextGlyph,\n    badge: \"bg-blue-50 text-blue-600 dark:bg-blue-300/10 dark:text-blue-300\",\n  },\n  number: {\n    icon: InputNumericGlyph,\n    badge:\n      \"bg-emerald-50 text-emerald-600 dark:bg-emerald-300/10 dark:text-emerald-300\",\n  },\n  integer: {\n    icon: InputNumericGlyph,\n    badge: \"bg-teal-50 text-teal-600 dark:bg-teal-300/10 dark:text-teal-300\",\n  },\n  boolean: {\n    icon: TextCheckGlyph,\n    badge:\n      \"bg-amber-50 text-amber-600 dark:bg-amber-300/10 dark:text-amber-300\",\n  },\n  null: {\n    icon: CancelCircleGlyph,\n    badge: \"bg-zinc-50 text-zinc-600 dark:bg-zinc-300/10 dark:text-zinc-300\",\n  },\n  object: {\n    icon: SourceCodeSquareGlyph,\n    badge:\n      \"bg-violet-50 text-violet-600 dark:bg-violet-300/10 dark:text-violet-300\",\n  },\n  array: {\n    icon: SecondBracketGlyph,\n    badge: \"bg-cyan-50 text-cyan-600 dark:bg-cyan-300/10 dark:text-cyan-300\",\n  },\n  enum: {\n    icon: LeftToRightListBulletGlyph,\n    badge: \"bg-rose-50 text-rose-600 dark:bg-rose-300/10 dark:text-rose-300\",\n  },\n  \"array-string\": {\n    icon: SecondBracketGlyph,\n    badge: \"bg-blue-50 text-blue-600 dark:bg-blue-300/10 dark:text-blue-300\",\n  },\n  \"array-number\": {\n    icon: SecondBracketGlyph,\n    badge:\n      \"bg-emerald-50 text-emerald-600 dark:bg-emerald-300/10 dark:text-emerald-300\",\n  },\n  \"array-integer\": {\n    icon: SecondBracketGlyph,\n    badge: \"bg-teal-50 text-teal-600 dark:bg-teal-300/10 dark:text-teal-300\",\n  },\n  \"array-boolean\": {\n    icon: SecondBracketGlyph,\n    badge:\n      \"bg-amber-50 text-amber-600 dark:bg-amber-300/10 dark:text-amber-300\",\n  },\n  \"array-enum\": {\n    icon: LeftToRightListBulletGlyph,\n    badge: \"bg-rose-50 text-rose-600 dark:bg-rose-300/10 dark:text-rose-300\",\n  },\n  \"array-object\": {\n    icon: SecondBracketGlyph,\n    badge:\n      \"bg-violet-50 text-violet-600 dark:bg-violet-300/10 dark:text-violet-300\",\n  },\n}\nconst CODE_FILE_THEME = {\n  \"--diffs-light-bg\": \"var(--color-code)\",\n  \"--diffs-dark-bg\": \"var(--color-code)\",\n  \"--diffs-light\": \"var(--color-code-foreground)\",\n  \"--diffs-dark\": \"var(--color-code-foreground)\",\n  \"--diffs-bg-context-override\": \"var(--color-code)\",\n  \"--diffs-bg-context-gutter-override\": \"var(--color-code)\",\n  \"--diffs-bg-buffer-override\": \"var(--color-code)\",\n  \"--diffs-fg-number-override\": \"var(--color-muted-foreground)\",\n  \"--diffs-font-size\": \"0.8rem\",\n  \"--diffs-line-height\": \"1.625\",\n} as React.CSSProperties\nconst CODE_FONT_SIZE_PX = 12.8\nconst CODE_LINE_HEIGHT_PX = CODE_FONT_SIZE_PX * 1.625\nconst CODE_VIRTUAL_FILE_METRICS = {\n  hunkLineCount: 50,\n  lineHeight: CODE_LINE_HEIGHT_PX,\n  diffHeaderHeight: 44,\n  spacing: 8,\n  paddingTop: 0,\n  paddingBottom: 8,\n} satisfies VirtualFileMetrics\nconst CODE_HIGHLIGHTER_OPTIONS = {\n  theme: {\n    light: \"pierre-light-soft\",\n    dark: \"pierre-dark-soft\",\n  },\n  langs: [\"json\"],\n} satisfies WorkerInitializationRenderOptions\nconst CODE_WORKER_POOL_OPTIONS = {\n  workerFactory: () =>\n    new Worker(new URL(\"@pierre/diffs/worker/worker.js\", import.meta.url), {\n      type: \"module\",\n    }),\n} satisfies WorkerPoolOptions\nfunction ScrollAreaVirtualizer({\n  children,\n  className,\n  contentClassName,\n  contentStyle,\n  scrollFade = true,\n}: {\n  children: React.ReactNode\n  className?: string\n  contentClassName?: string\n  contentStyle?: React.CSSProperties\n  scrollFade?: boolean\n}) {\n  const [virtualizer] = React.useState(() =>\n    typeof window !== \"undefined\" ? new DiffsVirtualizer() : undefined\n  )\n  const viewportRef = React.useRef<HTMLDivElement | null>(null)\n  const contentRef = React.useRef<HTMLDivElement | null>(null)\n  const syncVirtualizer = React.useCallback(() => {\n    if (!virtualizer) return\n    const viewport = viewportRef.current\n    const content = contentRef.current\n    if (viewport && content) {\n      virtualizer.setup(viewport, content)\n      return\n    }\n    virtualizer.cleanUp()\n  }, [virtualizer])\n  const setViewportRef = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      viewportRef.current = node\n      syncVirtualizer()\n    },\n    [syncVirtualizer]\n  )\n  const setContentRef = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      contentRef.current = node\n      syncVirtualizer()\n    },\n    [syncVirtualizer]\n  )\n  React.useEffect(() => {\n    return () => virtualizer?.cleanUp()\n  }, [virtualizer])\n  return (\n    <VirtualizerContext.Provider value={virtualizer}>\n      <InlineScrollArea2\n        className={className}\n        scrollFade={scrollFade}\n        scrollbarOverflowOnly\n        viewportRef={setViewportRef}\n      >\n        <div\n          ref={setContentRef}\n          className={contentClassName}\n          style={contentStyle}\n        >\n          {children}\n        </div>\n      </InlineScrollArea2>\n    </VirtualizerContext.Provider>\n  )\n}\nconst subscribeToHydration = () => () => {}\nfunction useResolvedCodeThemeType(theme?: SchemaBuilderTheme) {\n  const { resolvedTheme } = useTheme()\n  const isMounted = React.useSyncExternalStore(\n    subscribeToHydration,\n    () => true,\n    () => false\n  )\n  if (theme) return theme\n  return isMounted && resolvedTheme === \"dark\" ? \"dark\" : \"light\"\n}\nexport const SAMPLE_SCHEMA: SchemaBuilderSchema = {\n  properties: [\n    {\n      id: \"statement-period\",\n      key: \"statement_period\",\n      type: \"string\",\n      description: \"Date range covered by the bank statement.\",\n    },\n    {\n      id: \"account-type\",\n      key: \"account_type\",\n      type: \"enum\",\n      description: \"Account product shown on the statement.\",\n      enumValues: [\n        {\n          id: \"account-type-checking\",\n          value: \"checking\",\n          description: \"Standard checking account.\",\n        },\n        {\n          id: \"account-type-savings\",\n          value: \"savings\",\n          description: \"Savings or money market account.\",\n        },\n      ],\n    },\n    {\n      id: \"account-holder\",\n      key: \"account_holder\",\n      type: \"object\",\n      description: \"Person or business that owns the account.\",\n      properties: [\n        {\n          id: \"holder-name\",\n          key: \"name\",\n          type: \"string\",\n          description: \"Full account holder name.\",\n        },\n        {\n          id: \"holder-address\",\n          key: \"address\",\n          type: \"object\",\n          description: \"Mailing address for the account holder.\",\n          properties: [\n            {\n              id: \"holder-address-line-1\",\n              key: \"line_1\",\n              type: \"string\",\n              description: \"Street address line.\",\n            },\n            {\n              id: \"holder-address-city\",\n              key: \"city\",\n              type: \"string\",\n              description: \"City from the mailing address.\",\n            },\n          ],\n        },\n      ],\n    },\n    {\n      id: \"transactions\",\n      key: \"transactions\",\n      type: \"array\",\n      description: \"Posted account activity during the statement period.\",\n      items: {\n        type: \"object\",\n        properties: [\n          {\n            id: \"transaction-date\",\n            key: \"date\",\n            type: \"string\",\n            description: \"Posting date for the transaction.\",\n          },\n          {\n            id: \"transaction-description\",\n            key: \"description\",\n            type: \"string\",\n            description: \"Statement transaction description.\",\n          },\n          {\n            id: \"transaction-amount\",\n            key: \"amount\",\n            type: \"number\",\n            description: \"Signed transaction amount.\",\n          },\n        ],\n      },\n    },\n  ],\n}\nlet nextPropertyId = 0\nfunction createId(prefix: string) {\n  nextPropertyId += 1\n  return `${prefix}-${nextPropertyId}`\n}\nfunction createEnumValue(): SchemaBuilderEnumValue {\n  return {\n    id: createId(\"enum\"),\n    value: \"\",\n    description: \"\",\n  }\n}\nfunction createProperty(\n  type: SchemaBuilderFieldType = \"string\"\n): SchemaBuilderProperty {\n  return normalizePropertyForType({\n    id: createId(\"property\"),\n    key: \"\",\n    type,\n    description: \"\",\n  })\n}\nfunction normalizePropertyForType(\n  property: SchemaBuilderProperty,\n  type = property.type\n): SchemaBuilderProperty {\n  const base = {\n    ...property,\n    type,\n  }\n  if (type === \"enum\") {\n    return {\n      ...base,\n      enumValues: base.enumValues?.length\n        ? base.enumValues\n        : [createEnumValue()],\n      properties: undefined,\n      items: undefined,\n    }\n  }\n  if (type === \"object\") {\n    return {\n      ...base,\n      enumValues: undefined,\n      properties: base.properties?.length\n        ? base.properties\n        : [createProperty(\"string\")],\n      items: undefined,\n    }\n  }\n  if (type === \"array\") {\n    return {\n      ...base,\n      enumValues: undefined,\n      properties: undefined,\n      items: base.items ?? {\n        type: \"object\",\n        properties: [createProperty(\"string\")],\n      },\n    }\n  }\n  return {\n    ...base,\n    enumValues: undefined,\n    properties: undefined,\n    items: undefined,\n  }\n}\nfunction formatJson(value: unknown) {\n  return JSON.stringify(value, null, 2)\n}\nfunction serializeProperty(\n  property: SchemaBuilderProperty\n): SerializedSchemaProperty {\n  const description = property.description.trim()\n  const base = description ? { description } : {}\n  if (property.type === \"enum\") {\n    const enumValues = property.enumValues ?? []\n    return {\n      type: \"string\",\n      ...base,\n      enum: enumValues.map((option) => option.value),\n      enumDescriptions: Object.fromEntries(\n        enumValues\n          .filter((option) => option.description.trim())\n          .map((option) => [option.value, option.description])\n      ),\n    }\n  }\n  if (property.type === \"object\") {\n    return {\n      type: \"object\",\n      ...base,\n      properties: serializeProperties(property.properties ?? []),\n    }\n  }\n  if (property.type === \"array\") {\n    const items = property.items ?? { type: \"string\" as const }\n    return {\n      type: \"array\",\n      ...base,\n      items:\n        items.type === \"object\"\n          ? {\n              type: \"object\",\n              properties: serializeProperties(items.properties ?? []),\n            }\n          : items.type === \"enum\"\n            ? {\n                type: \"string\",\n                enum: (items.enumValues ?? []).map((option) => option.value),\n                enumDescriptions: Object.fromEntries(\n                  (items.enumValues ?? [])\n                    .filter((option) => option.description.trim())\n                    .map((option) => [option.value, option.description])\n                ),\n              }\n            : { type: items.type },\n    }\n  }\n  return {\n    type: property.type,\n    ...base,\n  }\n}\nfunction serializeProperties(\n  properties: SchemaBuilderProperty[]\n): Record<string, SerializedSchemaProperty> {\n  return Object.fromEntries(\n    properties\n      .filter((property) => property.key.trim())\n      .map((property) => [property.key.trim(), serializeProperty(property)])\n  )\n}\nexport function serializeSchema(schema: SchemaBuilderSchema): SerializedSchema {\n  return {\n    type: \"object\",\n    properties: serializeProperties(schema.properties),\n  }\n}\nfunction updatePropertyById(\n  properties: SchemaBuilderProperty[],\n  id: string,\n  update: (property: SchemaBuilderProperty) => SchemaBuilderProperty\n) {\n  return properties.map((property) =>\n    property.id === id ? update(property) : property\n  )\n}\nfunction getObjectContainerId(propertyId: string) {\n  return `object:${propertyId}`\n}\nfunction getArrayObjectContainerId(propertyId: string) {\n  return `array-object:${propertyId}`\n}\nfunction isPropertyContainerId(id: UniqueIdentifier) {\n  const value = String(id)\n  return (\n    value === ROOT_SCHEMA_CONTAINER_ID ||\n    value.startsWith(\"object:\") ||\n    value.startsWith(\"array-object:\")\n  )\n}\nfunction getPropertyChildContainerId(property: SchemaBuilderProperty) {\n  if (property.type === \"object\") {\n    return getObjectContainerId(property.id)\n  }\n  if (property.type === \"array\" && property.items?.type === \"object\") {\n    return getArrayObjectContainerId(property.id)\n  }\n  return null\n}\nfunction propertyHasNestedEditor(property: SchemaBuilderProperty) {\n  return (\n    property.type === \"enum\" ||\n    property.type === \"object\" ||\n    property.type === \"array\"\n  )\n}\nfunction getNestedEditorLabel(property: SchemaBuilderProperty) {\n  return `${property.type === \"enum\" ? \"Configure enums\" : \"Configure schema\"} for ${property.key || \"property\"}`\n}\ntype PropertyLocation = {\n  containerId: string\n  index: number\n  property: SchemaBuilderProperty\n}\nconst NOT_FOUND_PROPERTIES: SchemaBuilderProperty[] = []\nfunction findPropertyLocation(\n  properties: SchemaBuilderProperty[],\n  propertyId: string,\n  containerId = ROOT_SCHEMA_CONTAINER_ID\n): PropertyLocation | null {\n  for (let index = 0; index < properties.length; index += 1) {\n    const property = properties[index]\n    if (!property) continue\n    if (property.id === propertyId) {\n      return {\n        containerId,\n        index,\n        property,\n      }\n    }\n    if (property.type === \"object\") {\n      const nestedLocation = findPropertyLocation(\n        property.properties ?? [],\n        propertyId,\n        getObjectContainerId(property.id)\n      )\n      if (nestedLocation) return nestedLocation\n    }\n    if (property.type === \"array\" && property.items?.type === \"object\") {\n      const nestedLocation = findPropertyLocation(\n        property.items.properties ?? [],\n        propertyId,\n        getArrayObjectContainerId(property.id)\n      )\n      if (nestedLocation) return nestedLocation\n    }\n  }\n  return null\n}\nfunction propertyOwnsContainer(\n  property: SchemaBuilderProperty,\n  containerId: string\n): boolean {\n  if (property.type === \"object\") {\n    if (getObjectContainerId(property.id) === containerId) return true\n    if (\n      (property.properties ?? []).some((childProperty) =>\n        propertyOwnsContainer(childProperty, containerId)\n      )\n    ) {\n      return true\n    }\n  }\n  if (property.type === \"array\" && property.items?.type === \"object\") {\n    if (getArrayObjectContainerId(property.id) === containerId) return true\n    return (property.items.properties ?? []).some((childProperty) =>\n      propertyOwnsContainer(childProperty, containerId)\n    )\n  }\n  return false\n}\nfunction getContainerProperties(\n  properties: SchemaBuilderProperty[],\n  containerId: string\n): SchemaBuilderProperty[] {\n  if (containerId === ROOT_SCHEMA_CONTAINER_ID) return properties\n  for (const property of properties) {\n    if (property.type === \"object\") {\n      if (getObjectContainerId(property.id) === containerId) {\n        return property.properties ?? []\n      }\n      const nestedProperties = getContainerProperties(\n        property.properties ?? [],\n        containerId\n      )\n      if (nestedProperties !== NOT_FOUND_PROPERTIES) return nestedProperties\n    }\n    if (property.type === \"array\" && property.items?.type === \"object\") {\n      if (getArrayObjectContainerId(property.id) === containerId) {\n        return property.items.properties ?? []\n      }\n      const nestedProperties = getContainerProperties(\n        property.items.properties ?? [],\n        containerId\n      )\n      if (nestedProperties !== NOT_FOUND_PROPERTIES) return nestedProperties\n    }\n  }\n  return NOT_FOUND_PROPERTIES\n}\nfunction setContainerProperties(\n  properties: SchemaBuilderProperty[],\n  containerId: string,\n  nextContainerProperties: SchemaBuilderProperty[]\n): SchemaBuilderProperty[] {\n  if (containerId === ROOT_SCHEMA_CONTAINER_ID) return nextContainerProperties\n  return properties.map((property) => {\n    if (property.type === \"object\") {\n      if (getObjectContainerId(property.id) === containerId) {\n        return {\n          ...property,\n          properties: nextContainerProperties,\n        }\n      }\n      return {\n        ...property,\n        properties: setContainerProperties(\n          property.properties ?? [],\n          containerId,\n          nextContainerProperties\n        ),\n      }\n    }\n    if (property.type === \"array\" && property.items?.type === \"object\") {\n      if (getArrayObjectContainerId(property.id) === containerId) {\n        return {\n          ...property,\n          items: {\n            ...property.items,\n            properties: nextContainerProperties,\n          },\n        }\n      }\n      return {\n        ...property,\n        items: {\n          ...property.items,\n          properties: setContainerProperties(\n            property.items.properties ?? [],\n            containerId,\n            nextContainerProperties\n          ),\n        },\n      }\n    }\n    return property\n  })\n}\nfunction moveProperty(\n  properties: SchemaBuilderProperty[],\n  activeId: string,\n  overId: string\n) {\n  const activeLocation = findPropertyLocation(properties, activeId)\n  if (!activeLocation) return properties\n  const overLocation = findPropertyLocation(properties, overId)\n  const targetContainerId = overLocation?.containerId ?? overId\n  if (!isPropertyContainerId(targetContainerId)) return properties\n  if (propertyOwnsContainer(activeLocation.property, targetContainerId)) {\n    return properties\n  }\n  if (activeLocation.containerId === targetContainerId) {\n    if (!overLocation) return properties\n    const containerProperties = getContainerProperties(\n      properties,\n      activeLocation.containerId\n    )\n    const targetIndex = overLocation.index\n    if (activeLocation.index === targetIndex) return properties\n    return setContainerProperties(\n      properties,\n      activeLocation.containerId,\n      arrayMove(containerProperties, activeLocation.index, targetIndex)\n    )\n  }\n  const sourceProperties = getContainerProperties(\n    properties,\n    activeLocation.containerId\n  )\n  let nextProperties = setContainerProperties(\n    properties,\n    activeLocation.containerId,\n    sourceProperties.filter((property) => property.id !== activeId)\n  )\n  const refreshedOverLocation = overLocation\n    ? findPropertyLocation(nextProperties, overId)\n    : null\n  const targetProperties = getContainerProperties(\n    nextProperties,\n    targetContainerId\n  )\n  const targetIndex = refreshedOverLocation?.index ?? targetProperties.length\n  const nextTargetProperties = targetProperties.slice()\n  nextTargetProperties.splice(targetIndex, 0, activeLocation.property)\n  nextProperties = setContainerProperties(\n    nextProperties,\n    targetContainerId,\n    nextTargetProperties\n  )\n  return nextProperties\n}\ntype PropertyMovePreview = {\n  containerId: string\n  index: number\n  property: SchemaBuilderProperty\n}\nfunction getPropertyMovePreview(\n  properties: SchemaBuilderProperty[],\n  activeId: string,\n  overId: string\n): PropertyMovePreview | null {\n  const activeLocation = findPropertyLocation(properties, activeId)\n  if (!activeLocation) return null\n  const overLocation = findPropertyLocation(properties, overId)\n  const targetContainerId = overLocation?.containerId ?? overId\n  if (!isPropertyContainerId(targetContainerId)) return null\n  if (propertyOwnsContainer(activeLocation.property, targetContainerId)) {\n    return null\n  }\n  if (activeLocation.containerId === targetContainerId) return null\n  const targetProperties = getContainerProperties(properties, targetContainerId)\n  return {\n    containerId: targetContainerId,\n    index: overLocation?.index ?? targetProperties.length,\n    property: activeLocation.property,\n  }\n}\nfunction isSameContainerPropertyReorderReady(\n  properties: SchemaBuilderProperty[],\n  activeId: UniqueIdentifier,\n  overId: UniqueIdentifier,\n  args: SchemaCollisionArgs\n) {\n  const activeLocation = findPropertyLocation(properties, String(activeId))\n  const overLocation = findPropertyLocation(properties, String(overId))\n  if (!activeLocation || !overLocation) return true\n  if (activeLocation.containerId !== overLocation.containerId) return true\n  const overRect = getPropertyBlockRect(overId, args)\n  if (!overRect || !args.pointerCoordinates) return true\n  const threshold = Math.min(\n    PROPERTY_REORDER_EDGE_THRESHOLD_PX,\n    overRect.height / 2\n  )\n  if (activeLocation.index < overLocation.index) {\n    return args.pointerCoordinates.y >= overRect.bottom - threshold\n  }\n  if (activeLocation.index > overLocation.index) {\n    return args.pointerCoordinates.y <= overRect.top + threshold\n  }\n  return false\n}\nfunction isSchemaCollisionCandidate(\n  properties: SchemaBuilderProperty[],\n  id: UniqueIdentifier,\n  activeId: UniqueIdentifier,\n  args: SchemaCollisionArgs\n) {\n  if (id === activeId) return false\n  const value = String(id)\n  const isSchemaTarget =\n    isPropertyContainerId(value) ||\n    Boolean(findPropertyLocation(properties, value))\n  return (\n    isSchemaTarget &&\n    isSameContainerPropertyReorderReady(properties, activeId, id, args)\n  )\n}\nfunction isPointerBelowLastContainerProperty(\n  containerProperties: SchemaBuilderProperty[],\n  args: SchemaCollisionArgs\n) {\n  const lastProperty = containerProperties.at(-1)\n  if (!lastProperty || !args.pointerCoordinates) return true\n  const lastPropertyRect = getPropertyBlockRect(lastProperty.id, args)\n  if (!lastPropertyRect) return false\n  return args.pointerCoordinates.y >= lastPropertyRect.bottom\n}\nfunction getNextPropertyInsertionId(\n  properties: SchemaBuilderProperty[],\n  location: PropertyLocation\n) {\n  const targetProperties = getContainerProperties(\n    properties,\n    location.containerId\n  )\n  return targetProperties[location.index + 1]?.id ?? location.containerId\n}\nfunction resolveCrossContainerPropertyInsertionId(\n  properties: SchemaBuilderProperty[],\n  activeId: UniqueIdentifier,\n  overId: UniqueIdentifier,\n  args: SchemaCollisionArgs\n) {\n  const activeLocation = findPropertyLocation(properties, String(activeId))\n  const overLocation = findPropertyLocation(properties, String(overId))\n  if (!activeLocation || !overLocation || !args.pointerCoordinates) {\n    return overId\n  }\n  if (activeLocation.containerId === overLocation.containerId) {\n    return overId\n  }\n  const overRowRect = getPropertyRowRect(overId)\n  if (overRowRect) {\n    const overRowMiddle = overRowRect.top + overRowRect.height / 2\n    return args.pointerCoordinates.y < overRowMiddle\n      ? overId\n      : getNextPropertyInsertionId(properties, overLocation)\n  }\n  const overBlockRect = getPropertyBlockRect(overId, args)\n  if (!overBlockRect) return overId\n  const overBlockMiddle = overBlockRect.top + overBlockRect.height / 2\n  return args.pointerCoordinates.y < overBlockMiddle\n    ? overId\n    : getNextPropertyInsertionId(properties, overLocation)\n}\nfunction useSchemaBuilderSensors() {\n  return useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: {\n        distance: 6,\n      },\n    }),\n    useSensor(KeyboardSensor, {\n      coordinateGetter: sortableKeyboardCoordinates,\n    })\n  )\n}\nfunction useStableCallback<Args extends unknown[], Result>(\n  callback: (...args: Args) => Result\n) {\n  const callbackRef = React.useRef(callback)\n  React.useInsertionEffect(() => {\n    callbackRef.current = callback\n  })\n  return React.useCallback((...args: Args) => callbackRef.current(...args), [])\n}\nfunction useStableIds(ids: string[]) {\n  const idsKey = JSON.stringify(ids)\n  return React.useMemo(() => JSON.parse(idsKey) as string[], [idsKey])\n}\nfunction getTypeStyleKey(\n  property: SchemaBuilderProperty\n): SchemaBuilderTypeStyleKey {\n  if (property.type === \"array\" && property.items) {\n    return `array-${property.items.type}`\n  }\n  return property.type\n}\nfunction SchemaTypeBadge({\n  className,\n  type,\n}: {\n  className?: string\n  type: SchemaBuilderTypeStyleKey\n}) {\n  const style = TYPE_STYLES[type]\n  return (\n    <span\n      className={cn(\n        \"inline-flex min-w-0 shrink-0 items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium\",\n        style.badge,\n        className\n      )}\n    >\n      <style.icon className=\"size-3.5\" />\n      <span className=\"truncate\">{TYPE_LABELS[type]}</span>\n    </span>\n  )\n}\nfunction SchemaTypeMenuItem({\n  type,\n  onSelect,\n}: {\n  type: SchemaBuilderTypeStyleKey\n  onSelect: () => void\n}) {\n  return (\n    <DropdownMenuItem onClick={onSelect}>\n      <SchemaTypeBadge type={type} />\n    </DropdownMenuItem>\n  )\n}\nfunction SchemaTypeMenu({\n  property,\n  onChange,\n}: {\n  property: SchemaBuilderProperty\n  onChange: (property: SchemaBuilderProperty) => void\n}) {\n  const updateType = React.useCallback(\n    (type: SchemaBuilderFieldType) => {\n      onChange(normalizePropertyForType(property, type))\n    },\n    [onChange, property]\n  )\n  const updateArrayItemType = React.useCallback(\n    (itemType: SchemaBuilderArrayItemType) => {\n      onChange({\n        ...normalizePropertyForType(property, \"array\"),\n        items:\n          itemType === \"object\"\n            ? {\n                type: \"object\",\n                properties: property.items?.properties?.length\n                  ? property.items.properties\n                  : [createProperty(\"string\")],\n              }\n            : itemType === \"enum\"\n              ? {\n                  type: \"enum\",\n                  enumValues: property.items?.enumValues?.length\n                    ? property.items.enumValues\n                    : [createEnumValue()],\n                }\n              : { type: itemType },\n      })\n    },\n    [onChange, property]\n  )\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"sm\"\n            className=\"h-8 w-full min-w-0 justify-between overflow-hidden rounded-md px-2\"\n          >\n            <SchemaTypeBadge\n              className=\"max-w-full shrink\"\n              type={getTypeStyleKey(property)}\n            />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent align=\"start\" className=\"w-44\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>JSON types</DropdownMenuLabel>\n          {SCALAR_TYPES.map((type) => (\n            <SchemaTypeMenuItem\n              key={type}\n              type={type}\n              onSelect={() => updateType(type)}\n            />\n          ))}\n          <SchemaTypeMenuItem type=\"enum\" onSelect={() => updateType(\"enum\")} />\n          <SchemaTypeMenuItem\n            type=\"object\"\n            onSelect={() => updateType(\"object\")}\n          />\n        </DropdownMenuGroup>\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger>\n            <SchemaTypeBadge type=\"array\" />\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent className=\"w-44\">\n            <DropdownMenuGroup>\n              <DropdownMenuLabel>Nested</DropdownMenuLabel>\n              <SchemaTypeMenuItem\n                type=\"array-object\"\n                onSelect={() => updateArrayItemType(\"object\")}\n              />\n              <SchemaTypeMenuItem\n                type=\"array-enum\"\n                onSelect={() => updateArrayItemType(\"enum\")}\n              />\n            </DropdownMenuGroup>\n            <DropdownMenuSeparator />\n            <DropdownMenuGroup>\n              <DropdownMenuLabel>Scalars</DropdownMenuLabel>\n              {ARRAY_SCALAR_TYPES.map((type) => (\n                <SchemaTypeMenuItem\n                  key={type}\n                  type={`array-${type}`}\n                  onSelect={() => updateArrayItemType(type)}\n                />\n              ))}\n            </DropdownMenuGroup>\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction ArrayItemTypeMenu({\n  property,\n  onChange,\n}: {\n  property: SchemaBuilderProperty\n  onChange: (property: SchemaBuilderProperty) => void\n}) {\n  const items = property.items ?? { type: \"string\" as const }\n  const updateItemType = React.useCallback(\n    (type: SchemaBuilderArrayItemType) => {\n      onChange({\n        ...property,\n        items:\n          type === \"object\"\n            ? {\n                type: \"object\",\n                properties: items.properties?.length\n                  ? items.properties\n                  : [createProperty(\"string\")],\n              }\n            : type === \"enum\"\n              ? {\n                  type: \"enum\",\n                  enumValues: items.enumValues?.length\n                    ? items.enumValues\n                    : [createEnumValue()],\n                }\n              : { type },\n      })\n    },\n    [items.enumValues, items.properties, onChange, property]\n  )\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"outline\"\n            size=\"sm\"\n            className=\"h-6 px-2\"\n            onClick={(event) => event.stopPropagation()}\n            onPointerDown={(event) => event.stopPropagation()}\n          >\n            <SchemaTypeBadge type={`array-${items.type}`} />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>Nested</DropdownMenuLabel>\n          <SchemaTypeMenuItem\n            type=\"array-object\"\n            onSelect={() => updateItemType(\"object\")}\n          />\n          <SchemaTypeMenuItem\n            type=\"array-enum\"\n            onSelect={() => updateItemType(\"enum\")}\n          />\n        </DropdownMenuGroup>\n        <DropdownMenuSeparator />\n        <DropdownMenuGroup>\n          <DropdownMenuLabel>Scalars</DropdownMenuLabel>\n          {ARRAY_SCALAR_TYPES.map((type) => (\n            <SchemaTypeMenuItem\n              key={type}\n              type={`array-${type}`}\n              onSelect={() => updateItemType(type)}\n            />\n          ))}\n        </DropdownMenuGroup>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction InlineTextInput({\n  className,\n  onChange,\n  onInput,\n  ...props\n}: React.InputHTMLAttributes<HTMLInputElement>) {\n  const handleInput = React.useCallback(\n    (event: React.InputEvent<HTMLInputElement>) => {\n      onInput?.(event)\n      onChange?.(event as unknown as React.ChangeEvent<HTMLInputElement>)\n    },\n    [onChange, onInput]\n  )\n  return (\n    <input\n      className={cn(\n        \"h-9 w-full min-w-0 bg-transparent px-3 text-sm outline-none placeholder:text-muted-foreground/60 focus:bg-background\",\n        className\n      )}\n      onInput={handleInput}\n      {...props}\n    />\n  )\n}\nfunction EnumEditor({\n  values,\n  onChange,\n}: {\n  values: SchemaBuilderEnumValue[]\n  onChange: (values: SchemaBuilderEnumValue[]) => void\n}) {\n  const sensors = useSchemaBuilderSensors()\n  const dndContextId = React.useId()\n  const sortableItems = useStableIds(values.map((value) => value.id))\n  const updateValue = useStableCallback(\n    (\n      id: string,\n      update: (value: SchemaBuilderEnumValue) => SchemaBuilderEnumValue\n    ) => {\n      onChange(values.map((value) => (value.id === id ? update(value) : value)))\n    }\n  )\n  const handleDragEnd = React.useCallback(\n    (event: DragEndEvent) => {\n      const { active, over } = event\n      if (!over || active.id === over.id) return\n      const activeIndex = values.findIndex((value) => value.id === active.id)\n      const overIndex = values.findIndex((value) => value.id === over.id)\n      if (activeIndex < 0 || overIndex < 0) return\n      onChange(arrayMove(values, activeIndex, overIndex))\n    },\n    [onChange, values]\n  )\n  return (\n    <DndContext\n      id={`schema-builder-enum-${dndContextId}`}\n      sensors={sensors}\n      collisionDetection={closestCenter}\n      onDragEnd={handleDragEnd}\n    >\n      <div className=\"overflow-visible rounded-lg border bg-background\">\n        <table className=\"w-full table-fixed border-collapse text-sm\">\n          <thead>\n            <tr className=\"border-b bg-muted/55 text-xs text-muted-foreground\">\n              <th className=\"w-[34%] px-3 py-2 text-left font-medium\">Value</th>\n              <th className=\"border-l px-3 py-2 text-left font-medium\">\n                Description\n              </th>\n            </tr>\n          </thead>\n          <SortableContext\n            id={`schema-builder-enum-sortable-${dndContextId}`}\n            items={sortableItems}\n            strategy={verticalListSortingStrategy}\n          >\n            <tbody>\n              {values.map((value) => (\n                <SortableEnumRow\n                  key={value.id}\n                  value={value}\n                  onValueChange={updateValue}\n                />\n              ))}\n              <tr>\n                <td colSpan={2} className=\"p-0\">\n                  <button\n                    type=\"button\"\n                    className=\"flex h-9 w-full items-center justify-center gap-2 text-sm text-muted-foreground transition-colors outline-none hover:bg-muted/55 hover:text-foreground focus-visible:bg-muted/55 focus-visible:text-foreground\"\n                    onClick={() => onChange([...values, createEnumValue()])}\n                  >\n                    <IconPlaceholder\n                      lucide=\"Plus\"\n                      tabler=\"IconPlus\"\n                      hugeicons=\"Add01Icon\"\n                      phosphor=\"PlusIcon\"\n                      remixicon=\"RiAddLine\"\n                      className=\"size-4\"\n                    />\n                    Add enum value\n                  </button>\n                </td>\n              </tr>\n            </tbody>\n          </SortableContext>\n        </table>\n      </div>\n    </DndContext>\n  )\n}\nconst SortableEnumRow = React.memo(function SortableEnumRow({\n  value,\n  onValueChange,\n}: {\n  value: SchemaBuilderEnumValue\n  onValueChange: (\n    id: string,\n    update: (value: SchemaBuilderEnumValue) => SchemaBuilderEnumValue\n  ) => void\n}) {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({\n    id: value.id,\n    data: {\n      type: ENUM_VALUE_DRAG_TYPE,\n    },\n  })\n  return (\n    <tr\n      ref={setNodeRef}\n      className={cn(\n        \"group/enum-row border-b\",\n        isDragging && \"relative z-10 opacity-70\"\n      )}\n      style={{\n        transform: CSS.Translate.toString(transform),\n        transition,\n      }}\n    >\n      <td className=\"relative p-0 align-top\">\n        <button\n          type=\"button\"\n          className={cn(\n            \"absolute top-1/2 left-0 z-50 grid size-5 -translate-x-1/2 -translate-y-1/2 cursor-grab place-items-center rounded-md border bg-background text-muted-foreground opacity-0 shadow-sm transition-[opacity,color,box-shadow] outline-none group-hover/enum-row:opacity-100 hover:text-foreground focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing\",\n            isDragging && \"opacity-100\"\n          )}\n          aria-label={`Drag enum value ${value.value || \"empty value\"}`}\n          {...attributes}\n          {...listeners}\n        >\n          <IconPlaceholder\n            lucide=\"GripVertical\"\n            tabler=\"IconGripVertical\"\n            hugeicons=\"DragDropVerticalIcon\"\n            phosphor=\"DotsSixVerticalIcon\"\n            remixicon=\"RiDraggable\"\n            className=\"size-3.5\"\n          />\n        </button>\n        <div className=\"min-w-0\">\n          <InlineTextInput\n            value={value.value}\n            placeholder=\"approved\"\n            onChange={(event) =>\n              onValueChange(value.id, (current) => ({\n                ...current,\n                value: event.target.value,\n              }))\n            }\n          />\n        </div>\n      </td>\n      <td className=\"border-l p-0 align-top\">\n        <InlineTextInput\n          value={value.description}\n          placeholder=\"When the reviewer accepts the extracted value.\"\n          onChange={(event) =>\n            onValueChange(value.id, (current) => ({\n              ...current,\n              description: event.target.value,\n            }))\n          }\n        />\n      </td>\n    </tr>\n  )\n})\nfunction ArrayItemsEditor({\n  property,\n  onChange,\n  depth,\n  dropPreview,\n  nestedEditorOpenByPropertyId,\n  onNestedEditorOpenChange,\n}: {\n  property: SchemaBuilderProperty\n  onChange: (property: SchemaBuilderProperty) => void\n  depth: number\n  dropPreview: PropertyMovePreview | null\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n  onNestedEditorOpenChange: (propertyId: string, open: boolean) => void\n}) {\n  const items = property.items ?? { type: \"string\" as const }\n  return (\n    <>\n      {items.type === \"object\" ? (\n        <SchemaBuilderTable\n          properties={items.properties ?? []}\n          depth={depth + 1}\n          containerId={getArrayObjectContainerId(property.id)}\n          dropPreview={dropPreview}\n          nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n          onNestedEditorOpenChange={onNestedEditorOpenChange}\n          onPropertiesChange={(properties) =>\n            onChange({\n              ...property,\n              items: {\n                type: \"object\",\n                properties,\n              },\n            })\n          }\n        />\n      ) : items.type === \"enum\" ? (\n        <EnumEditor\n          values={items.enumValues ?? []}\n          onChange={(enumValues) =>\n            onChange({\n              ...property,\n              items: {\n                type: \"enum\",\n                enumValues,\n              },\n            })\n          }\n        />\n      ) : null}\n    </>\n  )\n}\nfunction NestedEditor({\n  property,\n  depth,\n  onChange,\n  dropPreview,\n  nestedEditorOpenByPropertyId,\n  onNestedEditorOpenChange,\n}: {\n  property: SchemaBuilderProperty\n  depth: number\n  onChange: (property: SchemaBuilderProperty) => void\n  dropPreview: PropertyMovePreview | null\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n  onNestedEditorOpenChange: (propertyId: string, open: boolean) => void\n}) {\n  if (property.type === \"enum\") {\n    return (\n      <EnumEditor\n        values={property.enumValues ?? []}\n        onChange={(enumValues) => onChange({ ...property, enumValues })}\n      />\n    )\n  }\n  if (property.type === \"object\") {\n    return (\n      <SchemaBuilderTable\n        properties={property.properties ?? []}\n        depth={depth + 1}\n        containerId={getObjectContainerId(property.id)}\n        dropPreview={dropPreview}\n        nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n        onNestedEditorOpenChange={onNestedEditorOpenChange}\n        onPropertiesChange={(properties) =>\n          onChange({ ...property, properties })\n        }\n      />\n    )\n  }\n  if (property.type === \"array\") {\n    return (\n      <ArrayItemsEditor\n        property={property}\n        depth={depth}\n        dropPreview={dropPreview}\n        nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n        onNestedEditorOpenChange={onNestedEditorOpenChange}\n        onChange={onChange}\n      />\n    )\n  }\n  return null\n}\nfunction SchemaBuilderTable({\n  properties,\n  depth = 0,\n  containerId = ROOT_SCHEMA_CONTAINER_ID,\n  dropPreview,\n  nestedEditorOpenByPropertyId,\n  onNestedEditorOpenChange,\n  onPropertiesChange,\n}: {\n  properties: SchemaBuilderProperty[]\n  depth?: number\n  containerId?: string\n  dropPreview: PropertyMovePreview | null\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n  onNestedEditorOpenChange: (propertyId: string, open: boolean) => void\n  onPropertiesChange: (properties: SchemaBuilderProperty[]) => void\n}) {\n  const { setNodeRef } = useDroppable({\n    id: containerId,\n    data: {\n      type: SCHEMA_PROPERTY_DRAG_TYPE,\n      containerId,\n    },\n  })\n  const sortableItems = useStableIds(properties.map((property) => property.id))\n  const updateProperty = useStableCallback(\n    (id: string, nextProperty: SchemaBuilderProperty) => {\n      onPropertiesChange(updatePropertyById(properties, id, () => nextProperty))\n    }\n  )\n  const addProperty = React.useCallback(() => {\n    onPropertiesChange([...properties, createProperty()])\n  }, [onPropertiesChange, properties])\n  const tableDropPreview =\n    dropPreview?.containerId === containerId ? dropPreview : null\n  return (\n    <div\n      ref={setNodeRef}\n      className=\"overflow-visible rounded-lg border bg-background\"\n    >\n      <table className=\"w-full table-fixed border-collapse text-sm\">\n        <thead>\n          <tr className=\"border-b bg-muted/55 text-xs text-muted-foreground\">\n            <th className=\"w-[24%] px-3 py-2 text-left font-medium sm:w-[27%]\">\n              Property key\n            </th>\n            <th className=\"w-[36%] border-l px-3 py-2 text-left font-medium sm:w-[23%]\">\n              Type\n            </th>\n            <th className=\"border-l px-3 py-2 text-left font-medium\">\n              Description\n            </th>\n          </tr>\n        </thead>\n        <SortableContext\n          id={containerId}\n          items={sortableItems}\n          strategy={verticalListSortingStrategy}\n        >\n          {properties.map((property, index) => (\n            <React.Fragment key={property.id}>\n              {tableDropPreview?.index === index ? (\n                <SchemaPropertyDropPreviewRows\n                  property={tableDropPreview.property}\n                />\n              ) : null}\n              <SortablePropertyRows\n                property={property}\n                depth={depth}\n                dropPreview={dropPreview}\n                nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n                onNestedEditorOpenChange={onNestedEditorOpenChange}\n                onPropertyChange={updateProperty}\n              />\n            </React.Fragment>\n          ))}\n          {tableDropPreview?.index === properties.length ? (\n            <SchemaPropertyDropPreviewRows\n              property={tableDropPreview.property}\n            />\n          ) : null}\n        </SortableContext>\n        <tbody>\n          <tr>\n            <td colSpan={3} className=\"p-0\">\n              <button\n                type=\"button\"\n                className=\"flex h-9 w-full items-center justify-center gap-2 text-sm text-muted-foreground transition-colors outline-none hover:bg-muted/55 hover:text-foreground focus-visible:bg-muted/55 focus-visible:text-foreground\"\n                onClick={addProperty}\n              >\n                <IconPlaceholder\n                  lucide=\"Plus\"\n                  tabler=\"IconPlus\"\n                  hugeicons=\"Add01Icon\"\n                  phosphor=\"PlusIcon\"\n                  remixicon=\"RiAddLine\"\n                  className=\"size-4\"\n                />\n                Add property\n              </button>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  )\n}\nconst SortablePropertyRows = React.memo(function SortablePropertyRows({\n  property,\n  depth,\n  dropPreview,\n  nestedEditorOpenByPropertyId,\n  onNestedEditorOpenChange,\n  onPropertyChange,\n}: {\n  property: SchemaBuilderProperty\n  depth: number\n  dropPreview: PropertyMovePreview | null\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n  onNestedEditorOpenChange: (propertyId: string, open: boolean) => void\n  onPropertyChange: (id: string, property: SchemaBuilderProperty) => void\n}) {\n  const {\n    attributes,\n    listeners,\n    setNodeRef,\n    transform,\n    transition,\n    isDragging,\n  } = useSortable({\n    id: property.id,\n    data: {\n      type: SCHEMA_PROPERTY_DRAG_TYPE,\n    },\n  })\n  const handlePropertyChange = React.useCallback(\n    (nextProperty: SchemaBuilderProperty) => {\n      onPropertyChange(nextProperty.id, nextProperty)\n    },\n    [onPropertyChange]\n  )\n  const hasNestedEditor = propertyHasNestedEditor(property)\n  const isNestedEditorOpen = nestedEditorOpenByPropertyId[property.id] ?? true\n  const nestedEditorLabel = getNestedEditorLabel(property)\n  return (\n    <tbody\n      ref={setNodeRef}\n      data-schema-builder-property-id={property.id}\n      className={cn(isDragging && \"relative z-10 opacity-70\")}\n      style={{\n        transform: CSS.Translate.toString(transform),\n        transition,\n      }}\n    >\n      <tr\n        data-schema-builder-property-row-id={property.id}\n        className={cn(\n          \"group/property-row border-b\",\n          hasNestedEditor && \"bg-muted/20\"\n        )}\n      >\n        <td className=\"relative p-0 align-top\">\n          <button\n            type=\"button\"\n            className={cn(\n              \"absolute top-1/2 left-0 z-50 grid size-5 -translate-x-1/2 -translate-y-1/2 cursor-grab place-items-center rounded-md border bg-background text-muted-foreground opacity-0 shadow-sm transition-[opacity,color,box-shadow] outline-none group-hover/property-row:opacity-100 hover:text-foreground focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring active:cursor-grabbing\",\n              isDragging && \"opacity-100\"\n            )}\n            aria-label={`Drag ${property.key || \"property\"}`}\n            {...attributes}\n            {...listeners}\n          >\n            <IconPlaceholder\n              lucide=\"GripVertical\"\n              tabler=\"IconGripVertical\"\n              hugeicons=\"DragDropVerticalIcon\"\n              phosphor=\"DotsSixVerticalIcon\"\n              remixicon=\"RiDraggable\"\n              className=\"size-3.5\"\n            />\n          </button>\n          <div className=\"min-w-0\">\n            <InlineTextInput\n              value={property.key}\n              placeholder={depth ? \"nested_key\" : \"property_key\"}\n              className=\"font-mono\"\n              spellCheck={false}\n              onChange={(event) =>\n                handlePropertyChange({\n                  ...property,\n                  key: event.target.value,\n                })\n              }\n            />\n          </div>\n        </td>\n        <td className=\"border-l p-1 align-top\">\n          <SchemaTypeMenu property={property} onChange={handlePropertyChange} />\n        </td>\n        <td className=\"border-l p-0 align-top\">\n          <InlineTextInput\n            value={property.description}\n            placeholder=\"Describe what this field should extract.\"\n            onChange={(event) =>\n              handlePropertyChange({\n                ...property,\n                description: event.target.value,\n              })\n            }\n          />\n        </td>\n      </tr>\n      {hasNestedEditor ? (\n        <tr className=\"border-b bg-muted/20\">\n          <td colSpan={3} className=\"p-0\">\n            <Collapsible\n              open={isNestedEditorOpen}\n              onOpenChange={(open) =>\n                onNestedEditorOpenChange(property.id, open)\n              }\n            >\n              <div className=\"group/collapsible-trigger-row flex h-8 w-full items-center transition-colors focus-within:bg-muted/55 hover:bg-muted/55\">\n                <CollapsibleTrigger\n                  className=\"flex h-full min-w-0 flex-1 items-center gap-2 px-3 text-left text-xs font-medium text-muted-foreground transition-colors outline-none group-hover/collapsible-trigger-row:text-foreground focus-visible:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n                  type=\"button\"\n                >\n                  <IconPlaceholder\n                    lucide=\"ChevronDown\"\n                    tabler=\"IconChevronDown\"\n                    hugeicons=\"ChevronDown\"\n                    phosphor=\"CaretDownIcon\"\n                    remixicon=\"RiArrowDownSLine\"\n                    className={cn(\n                      \"size-3.5 shrink-0 transition-transform duration-200\",\n                      !isNestedEditorOpen && \"-rotate-90\"\n                    )}\n                  />\n                  <span className=\"min-w-0 truncate\">{nestedEditorLabel}</span>\n                </CollapsibleTrigger>\n                {property.type === \"array\" ? (\n                  <div className=\"flex h-full shrink-0 items-center px-2\">\n                    <ArrayItemTypeMenu\n                      property={property}\n                      onChange={handlePropertyChange}\n                    />\n                  </div>\n                ) : null}\n              </div>\n              <CollapsiblePanel>\n                <div\n                  className=\"p-2 pl-[--schema-builder-nest-indent]\"\n                  style={\n                    {\n                      \"--schema-builder-nest-indent\": `${0.5 + Math.min(depth, 4) * 0.75}rem`,\n                    } as React.CSSProperties\n                  }\n                >\n                  <NestedEditor\n                    property={property}\n                    depth={depth}\n                    dropPreview={dropPreview}\n                    nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n                    onNestedEditorOpenChange={onNestedEditorOpenChange}\n                    onChange={handlePropertyChange}\n                  />\n                </div>\n              </CollapsiblePanel>\n            </Collapsible>\n          </td>\n        </tr>\n      ) : null}\n    </tbody>\n  )\n})\nfunction SchemaPropertyDropPreviewRows({\n  property,\n}: {\n  property: SchemaBuilderProperty\n}) {\n  return (\n    <tbody\n      className=\"pointer-events-none\"\n      aria-label={`Insert ${property.key || \"property\"} here`}\n    >\n      <tr className=\"h-0\">\n        <td colSpan={3} className=\"p-0\">\n          <div className=\"relative z-20 h-0 overflow-visible\">\n            <div className=\"absolute inset-x-2 top-0 h-px -translate-y-1/2 bg-primary\" />\n            <div className=\"absolute top-0 left-2 size-1.5 -translate-y-1/2 rounded-full bg-primary\" />\n          </div>\n        </td>\n      </tr>\n    </tbody>\n  )\n}\nfunction SchemaPropertyDragOverlay({\n  property,\n  isNestedEditorOpen,\n  nestedEditorOpenByPropertyId,\n}: {\n  property: SchemaBuilderProperty\n  isNestedEditorOpen: boolean\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n}) {\n  const hasNestedEditor = propertyHasNestedEditor(property)\n  return (\n    <div className=\"w-[min(680px,calc(100vw-2rem))] overflow-hidden rounded-lg border bg-background text-sm shadow-lg\">\n      <div\n        className={cn(\n          \"grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1.35fr)] items-center\",\n          hasNestedEditor && \"bg-muted/20\"\n        )}\n      >\n        <div className=\"min-w-0 px-3 py-2 font-mono\">\n          <span className=\"block truncate\">\n            {property.key || \"property_key\"}\n          </span>\n        </div>\n        <div className=\"border-l px-2 py-1.5\">\n          <SchemaTypeBadge type={getTypeStyleKey(property)} />\n        </div>\n        <div className=\"min-w-0 border-l px-3 py-2 text-muted-foreground\">\n          <span className=\"block truncate\">\n            {property.description || \"Describe what this field should extract.\"}\n          </span>\n        </div>\n      </div>\n      {hasNestedEditor ? (\n        <div className=\"border-t bg-muted/20\">\n          <div className=\"flex h-8 items-center gap-2 px-3 text-xs font-medium text-muted-foreground\">\n            <IconPlaceholder\n              lucide=\"ChevronDown\"\n              tabler=\"IconChevronDown\"\n              hugeicons=\"ChevronDown\"\n              phosphor=\"CaretDownIcon\"\n              remixicon=\"RiArrowDownSLine\"\n              className={cn(\n                \"size-3.5 shrink-0\",\n                !isNestedEditorOpen && \"-rotate-90\"\n              )}\n            />\n            <span className=\"min-w-0 truncate\">\n              {getNestedEditorLabel(property)}\n            </span>\n            {property.type === \"array\" ? (\n              <div className=\"ml-auto shrink-0\">\n                <SchemaTypeBadge\n                  type={`array-${property.items?.type ?? \"string\"}`}\n                />\n              </div>\n            ) : null}\n          </div>\n          {isNestedEditorOpen ? (\n            <div className=\"max-h-[280px] overflow-hidden p-2\">\n              <NestedEditorPreview\n                property={property}\n                nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n              />\n            </div>\n          ) : null}\n        </div>\n      ) : null}\n    </div>\n  )\n}\nfunction NestedEditorPreview({\n  property,\n  nestedEditorOpenByPropertyId,\n}: {\n  property: SchemaBuilderProperty\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n}) {\n  if (property.type === \"enum\") {\n    return <EnumValuesPreview values={property.enumValues ?? []} />\n  }\n  if (property.type === \"object\") {\n    return (\n      <SchemaPropertiesPreview\n        properties={property.properties ?? []}\n        nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n      />\n    )\n  }\n  if (property.type === \"array\") {\n    const items = property.items ?? { type: \"string\" as const }\n    if (items.type === \"enum\") {\n      return <EnumValuesPreview values={items.enumValues ?? []} />\n    }\n    if (items.type === \"object\") {\n      return (\n        <SchemaPropertiesPreview\n          properties={items.properties ?? []}\n          nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n        />\n      )\n    }\n  }\n  return null\n}\nfunction EnumValuesPreview({ values }: { values: SchemaBuilderEnumValue[] }) {\n  return (\n    <div className=\"overflow-hidden rounded-lg border bg-background\">\n      <div className=\"grid grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] border-b bg-muted/55 text-xs font-medium text-muted-foreground\">\n        <div className=\"px-3 py-2\">Value</div>\n        <div className=\"border-l px-3 py-2\">Description</div>\n      </div>\n      {values.map((value) => (\n        <div\n          key={value.id}\n          className=\"grid grid-cols-[minmax(0,0.85fr)_minmax(0,1.15fr)] border-b last:border-b-0\"\n        >\n          <div className=\"min-w-0 px-3 py-2 font-mono\">\n            <span className=\"block truncate\">{value.value || \"value\"}</span>\n          </div>\n          <div className=\"min-w-0 border-l px-3 py-2 text-muted-foreground\">\n            <span className=\"block truncate\">\n              {value.description || \"Description\"}\n            </span>\n          </div>\n        </div>\n      ))}\n    </div>\n  )\n}\nfunction SchemaPropertiesPreview({\n  properties,\n  nestedEditorOpenByPropertyId,\n}: {\n  properties: SchemaBuilderProperty[]\n  nestedEditorOpenByPropertyId: Record<string, boolean>\n}) {\n  return (\n    <div className=\"overflow-hidden rounded-lg border bg-background\">\n      <div className=\"grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1.25fr)] border-b bg-muted/55 text-xs font-medium text-muted-foreground\">\n        <div className=\"px-3 py-2\">Property key</div>\n        <div className=\"border-l px-3 py-2\">Type</div>\n        <div className=\"border-l px-3 py-2\">Description</div>\n      </div>\n      {properties.map((property) => {\n        const hasNestedEditor = propertyHasNestedEditor(property)\n        const isNestedEditorOpen =\n          nestedEditorOpenByPropertyId[property.id] ?? true\n        return (\n          <div key={property.id} className=\"border-b last:border-b-0\">\n            <div\n              className={cn(\n                \"grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1.25fr)] items-center\",\n                hasNestedEditor && \"bg-muted/20\"\n              )}\n            >\n              <div className=\"min-w-0 px-3 py-2 font-mono\">\n                <span className=\"block truncate\">\n                  {property.key || \"property_key\"}\n                </span>\n              </div>\n              <div className=\"border-l px-2 py-1.5\">\n                <SchemaTypeBadge type={getTypeStyleKey(property)} />\n              </div>\n              <div className=\"min-w-0 border-l px-3 py-2 text-muted-foreground\">\n                <span className=\"block truncate\">\n                  {property.description || \"Description\"}\n                </span>\n              </div>\n            </div>\n            {hasNestedEditor ? (\n              <div className=\"border-t bg-muted/20\">\n                <div className=\"flex h-7 items-center gap-2 px-3 text-xs font-medium text-muted-foreground\">\n                  <IconPlaceholder\n                    lucide=\"ChevronDown\"\n                    tabler=\"IconChevronDown\"\n                    hugeicons=\"ChevronDown\"\n                    phosphor=\"CaretDownIcon\"\n                    remixicon=\"RiArrowDownSLine\"\n                    className={cn(\n                      \"size-3.5 shrink-0\",\n                      !isNestedEditorOpen && \"-rotate-90\"\n                    )}\n                  />\n                  <span className=\"min-w-0 truncate\">\n                    {getNestedEditorLabel(property)}\n                  </span>\n                </div>\n                {isNestedEditorOpen ? (\n                  <div className=\"p-2\">\n                    <NestedEditorPreview\n                      property={property}\n                      nestedEditorOpenByPropertyId={\n                        nestedEditorOpenByPropertyId\n                      }\n                    />\n                  </div>\n                ) : null}\n              </div>\n            ) : null}\n          </div>\n        )\n      })}\n    </div>\n  )\n}\nexport const SchemaJsonView = React.memo(function SchemaJsonView({\n  scrollResetKey = 0,\n  schema,\n  theme,\n}: {\n  scrollResetKey?: React.Key\n  schema: SchemaBuilderSchema\n  theme?: SchemaBuilderTheme\n}) {\n  const codeThemeType = useResolvedCodeThemeType(theme)\n  const file = React.useMemo(() => {\n    const contents = formatJson(serializeSchema(schema))\n    return {\n      name: \"schema.json\",\n      contents,\n      lang: \"json\" as const,\n      cacheKey: contents,\n    }\n  }, [schema])\n  return (\n    <div\n      data-rehype-pretty-code-figure\n      className=\"relative m-0! h-full overflow-hidden rounded-none! bg-code text-code-foreground\"\n    >\n      <WorkerPoolContextProvider\n        poolOptions={CODE_WORKER_POOL_OPTIONS}\n        highlighterOptions={CODE_HIGHLIGHTER_OPTIONS}\n      >\n        <ScrollAreaVirtualizer\n          key={`${file.cacheKey}:${codeThemeType}:${String(scrollResetKey)}`}\n          className=\"h-full min-w-0\"\n          contentClassName=\"min-w-full\"\n        >\n          <File\n            key={`${file.cacheKey}:${codeThemeType}`}\n            className=\"block min-w-full\"\n            file={file}\n            metrics={CODE_VIRTUAL_FILE_METRICS}\n            style={CODE_FILE_THEME}\n            options={{\n              disableFileHeader: true,\n              overflow: \"scroll\",\n              themeType: codeThemeType,\n              theme: {\n                light: \"pierre-light-soft\",\n                dark: \"pierre-dark-soft\",\n              },\n            }}\n          />\n        </ScrollAreaVirtualizer>\n      </WorkerPoolContextProvider>\n    </div>\n  )\n})\nexport function SchemaBuilderPanel({\n  className,\n  defaultSchema = SAMPLE_SCHEMA,\n  schema: controlledSchema,\n  onSchemaChange,\n  theme,\n}: {\n  className?: string\n  defaultSchema?: SchemaBuilderSchema\n  schema?: SchemaBuilderSchema\n  onSchemaChange?: (schema: SchemaBuilderSchema) => void\n  theme?: SchemaBuilderTheme\n} = {}) {\n  const [activeTab, setActiveTab] = React.useState(\"form\")\n  const [jsonScrollResetKey, setJsonScrollResetKey] = React.useState(0)\n  const sensors = useSchemaBuilderSensors()\n  const dndContextId = React.useId()\n  const lastSchemaOverIdRef = React.useRef<UniqueIdentifier | null>(null)\n  const [activeDragProperty, setActiveDragProperty] =\n    React.useState<SchemaBuilderProperty | null>(null)\n  const [activeSchemaDrag, setActiveSchemaDrag] = React.useState<{\n    activeId: string\n    overId: string | null\n  } | null>(null)\n  const [nestedEditorOpenByPropertyId, setNestedEditorOpenByPropertyId] =\n    React.useState<Record<string, boolean>>({})\n  const [uncontrolledSchema, setUncontrolledSchema] =\n    React.useState(defaultSchema)\n  const schema = controlledSchema ?? uncontrolledSchema\n  // The JSON tab stays mounted, so feed it a schema frozen while it is\n  // hidden; otherwise every form keystroke re-serializes and re-highlights\n  // the code view. Syncing during render keeps it fresh once visible.\n  const [jsonViewSchema, setJsonViewSchema] = React.useState(schema)\n  if (activeTab === \"json\" && jsonViewSchema !== schema) {\n    setJsonViewSchema(schema)\n  }\n  const dropPreview = React.useMemo(() => {\n    if (!activeSchemaDrag?.overId) return null\n    return getPropertyMovePreview(\n      schema.properties,\n      activeSchemaDrag.activeId,\n      activeSchemaDrag.overId\n    )\n  }, [activeSchemaDrag, schema.properties])\n  const updateSchema = React.useCallback(\n    (nextSchema: SchemaBuilderSchema) => {\n      if (!controlledSchema) {\n        setUncontrolledSchema(nextSchema)\n      }\n      onSchemaChange?.(nextSchema)\n    },\n    [controlledSchema, onSchemaChange]\n  )\n  const handleNestedEditorOpenChange = React.useCallback(\n    (propertyId: string, open: boolean) => {\n      setNestedEditorOpenByPropertyId((current) => {\n        if ((current[propertyId] ?? true) === open) return current\n        return {\n          ...current,\n          [propertyId]: open,\n        }\n      })\n    },\n    []\n  )\n  const schemaCollisionDetection = React.useCallback<CollisionDetection>(\n    (args) => {\n      if (args.active.data.current?.type !== SCHEMA_PROPERTY_DRAG_TYPE) {\n        return closestCenter(args)\n      }\n      const pointerIntersections = pointerWithin(args)\n      const schemaPointerIntersections = pointerIntersections\n        .filter(({ id }) =>\n          isSchemaCollisionCandidate(\n            schema.properties,\n            id,\n            args.active.id,\n            args\n          )\n        )\n        .sort(\n          (first, second) =>\n            getDroppableRectArea(args.droppableRects, first.id) -\n            getDroppableRectArea(args.droppableRects, second.id)\n        )\n      const isPointerOverActive = pointerIntersections.some(\n        ({ id }) => id === args.active.id\n      )\n      if (\n        schemaPointerIntersections.length === 0 &&\n        isPointerOverActive &&\n        lastSchemaOverIdRef.current\n      ) {\n        return [{ id: lastSchemaOverIdRef.current }]\n      }\n      const intersections =\n        schemaPointerIntersections.length > 0\n          ? schemaPointerIntersections\n          : rectIntersection(args).filter(({ id }) =>\n              isSchemaCollisionCandidate(\n                schema.properties,\n                id,\n                args.active.id,\n                args\n              )\n            )\n      let overId = getFirstCollision(intersections, \"id\")\n      if (overId != null) {\n        overId =\n          getProjectedPropertyChildContainerId(\n            schema.properties,\n            overId,\n            args\n          ) ?? overId\n        if (isPropertyContainerId(overId)) {\n          const containerProperties = getContainerProperties(\n            schema.properties,\n            String(overId)\n          )\n          if (\n            containerProperties.length > 0 &&\n            !isPointerBelowLastContainerProperty(containerProperties, args)\n          ) {\n            const closestChild = closestCenter({\n              ...args,\n              droppableContainers: args.droppableContainers.filter(\n                (container) =>\n                  container.id !== overId &&\n                  containerProperties.some(\n                    (property) => property.id === container.id\n                  ) &&\n                  isSchemaCollisionCandidate(\n                    schema.properties,\n                    container.id,\n                    args.active.id,\n                    args\n                  )\n              ),\n            })\n            const closestChildId = getFirstCollision(closestChild, \"id\")\n            overId = closestChildId\n              ? (getProjectedPropertyChildContainerId(\n                  schema.properties,\n                  closestChildId,\n                  args\n                ) ?? closestChildId)\n              : overId\n          }\n        }\n        overId = resolveCrossContainerPropertyInsertionId(\n          schema.properties,\n          args.active.id,\n          overId,\n          args\n        )\n        lastSchemaOverIdRef.current = overId\n        return [{ id: overId }]\n      }\n      return lastSchemaOverIdRef.current\n        ? [{ id: lastSchemaOverIdRef.current }]\n        : []\n    },\n    [schema.properties]\n  )\n  const handleSchemaDragStart = React.useCallback(\n    (event: DragStartEvent) => {\n      if (event.active.data.current?.type !== SCHEMA_PROPERTY_DRAG_TYPE) {\n        return\n      }\n      lastSchemaOverIdRef.current = null\n      const activeId = String(event.active.id)\n      setActiveDragProperty(\n        findPropertyLocation(schema.properties, activeId)?.property ?? null\n      )\n      setActiveSchemaDrag({\n        activeId,\n        overId: null,\n      })\n    },\n    [schema.properties]\n  )\n  const handleSchemaDragOver = React.useCallback(\n    (event: DragOverEvent) => {\n      if (event.active.data.current?.type !== SCHEMA_PROPERTY_DRAG_TYPE) {\n        return\n      }\n      const activeId = String(event.active.id)\n      const eventOverId = event.over ? String(event.over.id) : null\n      const candidateOverIds = [\n        eventOverId,\n        lastSchemaOverIdRef.current\n          ? String(lastSchemaOverIdRef.current)\n          : null,\n      ]\n      const overId =\n        candidateOverIds.find(\n          (candidateOverId) =>\n            candidateOverId &&\n            candidateOverId !== activeId &&\n            getPropertyMovePreview(schema.properties, activeId, candidateOverId)\n        ) ?? null\n      setActiveSchemaDrag((current) => {\n        if (current?.activeId === activeId && current?.overId === overId) {\n          return current\n        }\n        return {\n          activeId,\n          overId,\n        }\n      })\n    },\n    [schema.properties]\n  )\n  const handleSchemaDragEnd = React.useCallback(\n    (event: DragEndEvent) => {\n      setActiveDragProperty(null)\n      setActiveSchemaDrag(null)\n      const { active, over } = event\n      if (active.data.current?.type !== SCHEMA_PROPERTY_DRAG_TYPE) {\n        lastSchemaOverIdRef.current = null\n        return\n      }\n      const activeId = String(active.id)\n      const candidateOverIds = [\n        over ? String(over.id) : null,\n        lastSchemaOverIdRef.current\n          ? String(lastSchemaOverIdRef.current)\n          : null,\n      ]\n      const overId =\n        candidateOverIds.find(\n          (candidateOverId) => candidateOverId && candidateOverId !== activeId\n        ) ?? null\n      lastSchemaOverIdRef.current = null\n      if (!overId) return\n      const nextProperties = moveProperty(schema.properties, activeId, overId)\n      if (nextProperties === schema.properties) return\n      updateSchema({\n        properties: nextProperties,\n      })\n    },\n    [schema.properties, updateSchema]\n  )\n  const handleSchemaDragCancel = React.useCallback(() => {\n    lastSchemaOverIdRef.current = null\n    setActiveDragProperty(null)\n    setActiveSchemaDrag(null)\n  }, [])\n  const handleTabChange = React.useCallback((nextTab: string) => {\n    setActiveTab(nextTab)\n    if (nextTab === \"json\") {\n      setJsonScrollResetKey((current) => current + 1)\n    }\n  }, [])\n  return (\n    <Tabs\n      value={activeTab}\n      onValueChange={handleTabChange}\n      className={cn(\"flex h-[560px] flex-col gap-0 bg-background\", className)}\n    >\n      <div className=\"flex min-h-12 items-center justify-between gap-3 border-b px-3\">\n        <TabsList className=\"h-8 sm:h-7\">\n          <TabsTrigger value=\"form\" className=\"h-7 sm:h-6\">\n            <IconPlaceholder\n              lucide=\"TableIcon\"\n              tabler=\"IconTable\"\n              hugeicons=\"TableIcon\"\n              phosphor=\"TableIcon\"\n              remixicon=\"RiTableLine\"\n              className=\"size-4\"\n            />\n            Form\n          </TabsTrigger>\n          <TabsTrigger value=\"json\" className=\"h-7 sm:h-6\">\n            <IconPlaceholder\n              lucide=\"SquareCode\"\n              tabler=\"IconSourceCode\"\n              hugeicons=\"SourceCodeSquareIcon\"\n              phosphor=\"CodeIcon\"\n              remixicon=\"RiCodeBlock\"\n              className=\"size-4\"\n            />\n            JSON\n          </TabsTrigger>\n        </TabsList>\n      </div>\n      <TabsContent value=\"form\" keepMounted className=\"min-h-0 flex-1\">\n        <InlineScrollArea2 className=\"h-full\" scrollFade>\n          <DndContext\n            id={`schema-builder-${dndContextId}`}\n            autoScroll={{\n              threshold: { x: 0.05, y: 0.14 },\n              acceleration: 8,\n            }}\n            sensors={sensors}\n            collisionDetection={schemaCollisionDetection}\n            measuring={SCHEMA_DND_MEASURING}\n            onDragStart={handleSchemaDragStart}\n            onDragOver={handleSchemaDragOver}\n            onDragEnd={handleSchemaDragEnd}\n            onDragCancel={handleSchemaDragCancel}\n          >\n            <div className=\"p-3\">\n              <SchemaBuilderTable\n                properties={schema.properties}\n                dropPreview={dropPreview}\n                nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n                onNestedEditorOpenChange={handleNestedEditorOpenChange}\n                onPropertiesChange={(properties) =>\n                  updateSchema({ properties })\n                }\n              />\n            </div>\n            <DragOverlay adjustScale={false}>\n              {activeDragProperty ? (\n                <SchemaPropertyDragOverlay\n                  property={activeDragProperty}\n                  isNestedEditorOpen={\n                    nestedEditorOpenByPropertyId[activeDragProperty.id] ?? true\n                  }\n                  nestedEditorOpenByPropertyId={nestedEditorOpenByPropertyId}\n                />\n              ) : null}\n            </DragOverlay>\n          </DndContext>\n        </InlineScrollArea2>\n      </TabsContent>\n      <TabsContent value=\"json\" keepMounted className=\"min-h-0 flex-1\">\n        <SchemaJsonView\n          scrollResetKey={jsonScrollResetKey}\n          schema={jsonViewSchema}\n          theme={theme}\n        />\n      </TabsContent>\n    </Tabs>\n  )\n}\ntype InlineRegistryIconProps = Omit<\n  React.ComponentProps<\"svg\">,\n  \"children\" | \"strokeWidth\"\n> & { strokeWidth?: number }\nfunction InlineScrollArea2({\n  className,\n  children,\n  orientation = \"both\",\n  scrollFade = false,\n  scrollbarGutter = false,\n  scrollbarOverflowOnly = false,\n  viewportClassName,\n  viewportProps,\n  viewportRef,\n  ...props\n}: InlineScrollAreaProps) {\n  const {\n    className: viewportPropsClassName,\n    ref: viewportPropsRef,\n    ...resolvedViewportProps\n  } = viewportProps ?? {}\n  const composedViewportRef = React.useMemo(\n    () => InlineComposeRefs(viewportPropsRef, viewportRef),\n    [viewportPropsRef, viewportRef]\n  )\n\n  if (\n    !viewportProps &&\n    !viewportRef &&\n    !viewportClassName &&\n    !scrollFade &&\n    !scrollbarGutter &&\n    !scrollbarOverflowOnly\n  ) {\n    return (\n      <InlineScrollArea\n        {...props}\n        className={cn(\n          \"size-full min-h-0\",\n          orientation === \"horizontal\" &&\n            \"[&>[data-orientation=vertical]]:hidden\",\n          className\n        )}\n      >\n        {children}\n        {orientation !== \"vertical\" ? (\n          <ScrollBar orientation=\"horizontal\" />\n        ) : null}\n      </InlineScrollArea>\n    )\n  }\n\n  return (\n    <ScrollAreaPrimitive.Root\n      className={cn(\n        \"size-full min-h-0\",\n        scrollbarOverflowOnly &&\n          \"[&:not(:has([data-slot=scroll-area-viewport][data-has-overflow-x]))_[data-orientation=horizontal]]:hidden [&:not(:has([data-slot=scroll-area-viewport][data-has-overflow-y]))_[data-orientation=vertical]]:hidden\",\n        className\n      )}\n      {...props}\n    >\n      <ScrollAreaPrimitive.Viewport\n        {...resolvedViewportProps}\n        ref={composedViewportRef}\n        className={cn(\n          \"h-full rounded-[inherit] outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          scrollFade &&\n            \"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] [--fade-size:1.5rem]\",\n          scrollbarGutter && orientation !== \"vertical\" && \"pb-3.5\",\n          scrollbarGutter && orientation !== \"horizontal\" && \"pe-3.5\",\n          viewportPropsClassName,\n          viewportClassName\n        )}\n        data-slot=\"scroll-area-viewport\"\n      >\n        {children}\n      </ScrollAreaPrimitive.Viewport>\n      {orientation !== \"horizontal\" ? (\n        <ScrollBar orientation=\"vertical\" />\n      ) : null}\n      {orientation !== \"vertical\" ? (\n        <ScrollBar orientation=\"horizontal\" />\n      ) : null}\n      {orientation === \"both\" ? <ScrollAreaPrimitive.Corner /> : null}\n    </ScrollAreaPrimitive.Root>\n  )\n}\ntype InlineScrollAreaProps = ScrollAreaPrimitive.Root.Props & {\n  orientation?: \"vertical\" | \"horizontal\" | \"both\"\n  scrollFade?: boolean\n  scrollbarGutter?: boolean\n  scrollbarOverflowOnly?: boolean\n  viewportClassName?: string\n  viewportProps?: ScrollAreaPrimitive.Viewport.Props\n  viewportRef?: React.Ref<HTMLDivElement>\n}\nfunction InlineComposeRefs<T>(...refs: Array<React.Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      if (!ref) continue\n      if (typeof ref === \"function\") ref(node)\n      else ref.current = node\n    }\n  }\n}\n",
      "type": "registry:ui",
      "target": "@components/extend/schema-builder.tsx"
    }
  ],
  "categories": [
    "documents"
  ],
  "type": "registry:ui"
}
