import { z } from "zod";
import type { BlockMdxConfig } from "../types.js";
import type { NestedBlock } from "../types.js";
/**
* Pure (React-free) part of the standard `columns` block: its data schema and
* MDX round-trip config. Shared by the server MDX adapter (a plan/content app
* registers it via `@agent-native/core/blocks/server`) and the full client spec
* (`columns.tsx`). Keeping this React-free means importing it into a server
* module never pulls React into the Nitro/SSR bundle.
*
* `columns` is a STANDARD library block: a multi-column side-by-side container
* where each column holds a list of child blocks and an optional header label
* (e.g. "Before"/"After"). The children are rendered RECURSIVELY through the
* app's own block dispatcher (`ctx.renderBlock`), so registered children render
* via their spec and unconverted children still fall through the app's legacy
* switch — the coexistence seam. It mirrors `tabs` exactly, only laid out as a
* grid instead of a pill rail.
*
* Its schema MUST stay data-compatible with the plan `columns` branch of
* `planBlockSchema` (`columns[]` of `{ id, label?, blocks: Block[] }`). The
* registry MDX config below keeps the compact self-closing `` encoding for generic apps and backward compatibility. The
* Plan app also accepts and exports a more human-editable source form:
* `…markdown and block components…`.
*/
/** One column: an optional label and the child blocks it contains. */
export interface ColumnsColumn {
id: string;
/** Optional per-column header (e.g. "Before"/"After"). */
label?: string;
/**
* Child blocks. Typed loosely as {@link NestedBlock} because the app owns the
* authoritative recursive block union (`planBlockSchema`); the columns spec
* only validates the column envelope (`id`/`label`) and passes children
* through.
*/
blocks: NestedBlock[];
}
export interface ColumnsData {
columns: ColumnsColumn[];
}
/** Matches the plan `idSchema` (`z.string().trim().min(1).max(120)`). */
const columnIdSchema = z.string().trim().min(1).max(120);
/**
* Child blocks are validated by the app's own recursive `planBlockSchema` when
* the plan persists; here they pass through untyped (`z.any()`) so core never
* needs to import an app-specific block union. The column envelope (`id`/`label`)
* mirrors the plan columns schema bounds exactly; a layout can temporarily have
* one remaining column after deleting another column's final block, and tops
* out at four to stay legible.
*/
export const columnsSchema = z.object({
columns: z
.array(
z.object({
id: columnIdSchema,
label: z.string().trim().min(1).max(120).optional(),
blocks: z.array(z.any()).max(40),
}),
)
.min(1)
.max(4),
}) as unknown as z.ZodType;
/**
* MDX config: `columns` is a single JSON-encoded attribute and the block is
* self-closing — the `` form. The entire `columns`
* array (labels + nested child blocks) is one JSON prop; child blocks are NOT
* serialized as nested MDX, mirroring how `tabs` encodes its `tabs` array.
* `toAttrs` returns only `columns`; `fromAttrs` reads the `columns` array
* (defaulting to `[]` for backward-compat with malformed/empty stored blocks).
*/
export const columnsMdx: BlockMdxConfig = {
tag: "Columns",
toAttrs: (data) => ({ columns: data.columns }),
fromAttrs: (attrs) => ({
columns: (attrs.array("columns") ?? []) as ColumnsColumn[],
}),
};