// src/appDom/index.ts
import { nanoid } from "nanoid/non-secure";
import { generateKeyBetween } from "fractional-indexing";
import invariant from "invariant";
import { guessTitle, pascalCase, removeDiacritics, uncapitalize } from "@mui/toolpad-utils/strings";
import { mapProperties, mapValues, hasOwnProperty } from "@mui/toolpad-utils/collections";

// src/utils/immutability.ts
function update(dest, src) {
  let result;
  Object.entries(src).forEach(([key, value]) => {
    if (dest[key] !== value) {
      result = result || { ...dest };
      result[key] = value;
    }
  });
  return result || dest;
}
function updateOrCreate(dest, src) {
  return dest ? update(dest, src) : src;
}
function omit(obj, ...keys) {
  let result;
  keys.forEach((key) => {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      if (!result) {
        result = { ...obj };
      }
      delete result[key];
    }
  });
  return result || obj;
}

// src/server/schema.ts
import { z } from "zod";
var API_VERSION = "v1";
function toolpadObjectSchema(kind, spec) {
  return z.object({
    apiVersion: z.literal("v1").describe(
      `Defines the version of this object. Used in determining compatibility between Toolpad "${kind}" objects.`
    ),
    kind: z.literal(kind).describe(`Describes the nature of this Toolpad "${kind}" object.`),
    spec: spec.optional().describe(`Defines the shape of this "${kind}" object`)
  });
}
var literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
function nameValuePairSchema(valueType) {
  return z.object({
    name: z.string().describe("The name"),
    value: valueType.describe(valueType.description ?? "The value")
  }).describe("A name/value pair.");
}
var jsExpressionBindingSchema = z.object({
  $$jsExpression: z.string().describe("The expression to be evaluated.")
}).describe("A binding that evaluates an expression and returns the result.");
var envBindingSchema = z.object({
  $$env: z.string().describe("The name of an environment variable.")
}).describe("An environment variable.");
function bindableSchema(valueType) {
  return z.union([valueType, jsExpressionBindingSchema, envBindingSchema]);
}
var jsExpressionActionSchema = z.object({
  $$jsExpressionAction: z.string().describe("The code to be executed.")
}).describe("A javascript expression to be executed when this action is triggered.");
var navigationActionSchema = z.object({
  $$navigationAction: z.object({
    page: z.string().describe("The page that is being navigated to"),
    parameters: z.record(bindableSchema(z.any())).describe("Parameters to pass when navigating to this page")
  })
}).describe(
  "A navigation from one page to another, optionally passing parameters to the next page."
);
var fetchModeSchema = z.union([
  z.literal("query").describe("Fetch automatically when the page opens"),
  z.literal("mutation").describe("Fetch on manual action only")
]);
var nameStringValuePairSchema = nameValuePairSchema(z.string()).describe(
  "a name/value pair with a string value."
);
var rawBodySchema = z.object({
  kind: z.literal("raw"),
  content: bindableSchema(z.string()),
  contentType: z.string()
});
var bindableNameStringValueSchema = nameValuePairSchema(bindableSchema(z.string())).describe(
  "A name/value pair where the value is dynamically bindable to strings."
);
var urlEncodedBodySchema = z.object({
  kind: z.literal("urlEncoded"),
  content: z.array(bindableNameStringValueSchema)
});
var fetchBodySchema = z.discriminatedUnion("kind", [rawBodySchema, urlEncodedBodySchema]);
var rawResponseTypeSchema = z.object({
  kind: z.literal("raw")
}).describe("Don't interpret this body at all.");
var jsonResponseTypeSchema = z.object({
  kind: z.literal("json")
}).describe("Interpret the fetch response as JSON");
var csvResponseTypeSchema = z.object({
  kind: z.literal("csv"),
  headers: z.boolean().describe("First row contains headers")
}).describe("Interpret the fetch response as CSV");
var xmlResponseTypeSchema = z.object({
  kind: z.literal("xml")
}).describe("Interpret the fetch response as XML");
var responseTypeSchema = z.discriminatedUnion("kind", [
  rawResponseTypeSchema,
  jsonResponseTypeSchema,
  csvResponseTypeSchema,
  xmlResponseTypeSchema
]).describe("Describes how a the fetch response is to be interpreted.");
var fetchQueryConfigSchema = z.object({
  kind: z.literal("rest").describe("Designates this object as a fetch query."),
  url: bindableSchema(z.string()).optional().describe("The URL of the request"),
  method: z.string().optional().describe("The request method."),
  headers: z.array(bindableNameStringValueSchema).optional().describe("Extra request headers."),
  searchParams: z.array(bindableNameStringValueSchema).optional().describe("Extra url query parameters."),
  body: fetchBodySchema.optional().describe("The request body."),
  transformEnabled: z.boolean().optional().describe("Run a custom transformer on the response."),
  transform: z.string().optional().describe("The custom transformer to run when enabled."),
  response: responseTypeSchema.optional().describe("How to parse the response.")
});
var localQueryConfigSchema = z.object({
  kind: z.literal("local").describe("Designates this object as a local function query."),
  function: z.string().optional().describe("The function to be executed on the backend by this query.")
});
var queryConfigSchema = z.discriminatedUnion("kind", [
  fetchQueryConfigSchema,
  localQueryConfigSchema
]);
var querySchema = z.object({
  name: z.string().describe("A name for the query"),
  enabled: bindableSchema(z.boolean()).optional().describe(
    "Activates or deactivates the query. When deactivated the data won't be loaded when the page opens."
  ),
  parameters: z.array(nameValuePairSchema(bindableSchema(z.any()))).optional().describe("Parameters to pass to this query."),
  mode: fetchModeSchema.optional().describe("How to fetch this query."),
  query: queryConfigSchema.optional().describe("Query definition"),
  transform: z.string().optional().describe("Transformation to run on the response"),
  transformEnabled: z.boolean().optional().describe("Enable the transformation"),
  refetchInterval: z.number().optional().describe("Interval to rerun this query at"),
  cacheTime: z.number().optional().describe("Time to cache before refetching")
});
var elementSchema;
var templateSchema = z.object({
  $$template: z.lazy(() => z.array(elementSchema)).describe("The subtree, that describes the UI to be rendered by the template.")
}).describe("Describes a fragment of Toolpad elements, to be used as a template.");
var baseElementSchema = z.object({
  component: z.string().describe("The component that this element was based on."),
  name: z.string().describe("a name for this component, which is used to reference it inside bindings."),
  layout: z.object({
    horizontalAlign: z.string().optional().describe("Lays out the element along the horizontal axis."),
    verticalAlign: z.string().optional().describe("Lays out the element along the vertical axis."),
    columnSize: z.number().optional().describe("The width this element takes up, expressed in terms of columns on the page.")
  }).optional().describe("Layout properties for this element.")
});
var bindablePropSchema = z.lazy(
  () => z.union([
    ...literalSchema.options,
    z.array(bindablePropSchema),
    z.record(
      z.string().refine((key) => !key.startsWith("$$")),
      bindablePropSchema
    ),
    jsExpressionBindingSchema,
    envBindingSchema,
    jsExpressionActionSchema,
    navigationActionSchema,
    templateSchema
  ])
);
elementSchema = baseElementSchema.extend({
  children: z.lazy(() => z.array(elementSchema).optional()).describe("The children of this element."),
  props: z.lazy(() => z.record(bindablePropSchema).optional()).describe("The properties to configure this instance of the component.")
}).describe("The instance of a component. Used to build user interfaces in pages.");
var applicationSchema = toolpadObjectSchema(
  "application",
  z.object({
    authorization: z.object({
      roles: z.array(
        z.union([
          z.string(),
          z.object({
            name: z.string().describe("The name of the role."),
            description: z.string().optional().describe("A description of the role.")
          })
        ])
      ).optional().describe("Available roles for this application. These can be assigned to users.")
    }).optional().describe("Authorization configuration for this application.")
  })
);
var pageSchema = toolpadObjectSchema(
  "page",
  z.object({
    displayName: z.string().optional().describe("Page name to display in the UI."),
    id: z.string().optional().describe("Serves as a canonical id of the page. Deprecated: use an alias instead."),
    alias: z.array(z.string()).optional().describe("Page name aliases."),
    title: z.string().optional().describe("Title for this page."),
    parameters: z.array(nameStringValuePairSchema).optional().describe("Parameters for the page. These can be set inside of the url query string."),
    queries: z.array(querySchema).optional().describe("Queries that are used by the page. These will load data when the page opens."),
    content: z.array(elementSchema).optional().describe("The content of the page. This defines the UI."),
    authorization: z.object({
      allowAll: z.boolean().optional().describe("Allow all users to access this page."),
      allowedRoles: z.array(z.string()).optional().describe("Roles that are allowed to access this page.")
    }).optional().describe("Authorization configuration for this page."),
    unstable_codeFile: z.coerce.boolean().optional().describe("The content of the page as JSX. Experimental, do not use!."),
    display: z.union([
      z.literal("standalone").describe("Hide the Toolpad chrome and just display the content of the page"),
      z.literal("shell").describe("Show Toolpad header and navigation.")
    ]).optional().describe(
      "Display mode of the page. This can also be set at runtime with the toolpad-display query parameter"
    )
  })
);
var simplePaletteColorOptionsSchema = z.object({
  main: z.string(),
  light: z.string().optional(),
  dark: z.string().optional(),
  contrastText: z.string().optional()
});
var themeOptionsSchema = z.object({
  // TODO: expand to full MUI theme object
  palette: z.object({
    mode: z.union([z.literal("light"), z.literal("dark")]).optional(),
    primary: simplePaletteColorOptionsSchema.optional(),
    secondary: simplePaletteColorOptionsSchema.optional()
  }).passthrough().optional()
}).passthrough();
var themeSchema = toolpadObjectSchema(
  "theme",
  z.object({
    options: themeOptionsSchema.optional().describe("The ThemeOptions object that gets fed into MUI's createTheme function.")
  })
);

// src/appDom/index.ts
var CURRENT_APPDOM_VERSION = 7;
function createFractionalIndex(index1, index2) {
  return generateKeyBetween(index1, index2);
}
function compareFractionalIndex(index1, index2) {
  if (index1 === index2) {
    return 0;
  }
  return index1 > index2 ? 1 : -1;
}
function isType(node, type) {
  return node.type === type;
}
function assertIsType(node, type) {
  invariant(isType(node, type), `Expected node type "${type}" but got "${node.type}"`);
}
function createId() {
  return nanoid(7);
}
function createSecret(value) {
  return { $$secret: value };
}
function getMaybeNode(dom, nodeId, type) {
  const node = dom.nodes[nodeId];
  if (!node) {
    return null;
  }
  if (type) {
    assertIsType(node, type);
  }
  return node;
}
function getNode(dom, nodeId, type) {
  const node = getMaybeNode(dom, nodeId, type);
  if (!node) {
    throw new Error(`Node "${nodeId}" not found`);
  }
  return node;
}
function assertIsApp(node) {
  assertIsType(node, "app");
}
function isPage(node) {
  return isType(node, "page");
}
function assertIsPage(node) {
  assertIsType(node, "page");
}
function isElement(node) {
  return isType(node, "element");
}
function assertIsElement(node) {
  assertIsType(node, "element");
}
function isQuery(node) {
  return isType(node, "query");
}
function isMutation(node) {
  return isType(node, "mutation");
}
function getRoot(dom) {
  return getNode(dom, dom.root);
}
function getApp(dom) {
  const app = getRoot(dom);
  assertIsApp(app);
  return app;
}
var childrenMemo = /* @__PURE__ */ new WeakMap();
function getChildNodes(dom, parent) {
  let domChildrenMemo = childrenMemo.get(dom);
  if (!domChildrenMemo) {
    domChildrenMemo = /* @__PURE__ */ new Map();
    childrenMemo.set(dom, domChildrenMemo);
  }
  let result = domChildrenMemo.get(parent.id);
  if (!result) {
    result = {};
    domChildrenMemo.set(parent.id, result);
    const allNodeChildren = Object.values(dom.nodes).filter(
      (node) => node.parentId === parent.id
    );
    for (const child of allNodeChildren) {
      const prop = child.parentProp || "children";
      let existing = result[prop];
      if (!existing) {
        existing = [];
        result[prop] = existing;
      }
      existing.push(child);
    }
    for (const childArray of Object.values(result)) {
      childArray?.sort((node1, node2) => {
        invariant(
          node1.parentIndex && node2.parentIndex,
          `Nodes inside the dom should have a parentIndex if they have a parent`
        );
        return compareFractionalIndex(node1.parentIndex, node2.parentIndex);
      });
    }
  }
  return result;
}
function getParent(dom, child) {
  child = getNode(dom, child.id, child.type);
  if (child.parentId) {
    const parent = getNode(dom, child.parentId);
    return parent;
  }
  return null;
}
function createNodeInternal(id, type, init) {
  return {
    ...init,
    id,
    type,
    parentId: null,
    parentProp: null,
    parentIndex: null
  };
}
function slugifyNodeName(nameCandidate, fallback) {
  let slug = nameCandidate;
  slug = slug.trim();
  slug = removeDiacritics(slug);
  const [first, ...rest] = slug.split(/\s+/);
  slug = first + pascalCase(...rest);
  slug = slug.replace(/[^a-zA-Z0-9]+/g, "_");
  slug = slug.replace(/^\d+/g, "");
  if (!slug) {
    slug = fallback;
  }
  return slug;
}
function createNode(dom, type, init) {
  const id = createId();
  const name = slugifyNodeName(init.name || type, type);
  return createNodeInternal(id, type, {
    ...init,
    name
  });
}
function createFragmentInternal(id, type, init) {
  return {
    nodes: {
      [id]: createNodeInternal(id, type, init)
    },
    root: id,
    version: CURRENT_APPDOM_VERSION
  };
}
function createFragment(type, init) {
  const rootId = createId();
  return createFragmentInternal(rootId, type, init);
}
function createDom() {
  return createFragment("app", {
    name: "Application",
    attributes: {}
  });
}
function createElement(dom, component, props = {}, layout = {}, name) {
  return createNode(dom, "element", {
    name: name || uncapitalize(component),
    props,
    attributes: {
      component
    },
    layout
  });
}
function getDescendants(dom, node) {
  const children = Object.values(getChildNodes(dom, node)).flat().filter(Boolean);
  return [...children, ...children.flatMap((child) => getDescendants(dom, child))];
}
function getPageAncestor(dom, node) {
  if (isPage(node)) {
    return node;
  }
  const parent = getParent(dom, node);
  if (parent) {
    return getPageAncestor(dom, parent);
  }
  return null;
}
function getExistingNamesForChildren(dom, parent, parentProp) {
  const pageNode = getPageAncestor(dom, parent);
  if (pageNode) {
    const pageDescendants = getDescendants(dom, pageNode);
    return new Set(pageDescendants.map((scopeNode) => scopeNode.name));
  }
  if (parentProp) {
    const childNodes = getChildNodes(dom, parent);
    const { [parentProp]: children = [] } = childNodes;
    return new Set(children.map((scopeNode) => scopeNode.name));
  }
  const descendants = getDescendants(dom, parent);
  return new Set(descendants.map((scopeNode) => scopeNode.name));
}
function proposeName(candidate, disallowedNames = /* @__PURE__ */ new Set()) {
  const slug = slugifyNodeName(candidate, "node");
  if (!disallowedNames.has(slug)) {
    return slug;
  }
  const basename = candidate.replace(/\d+$/, "");
  let counter = 1;
  while (disallowedNames.has(basename + counter)) {
    counter += 1;
  }
  return basename + counter;
}
function setNamespacedProp(node, namespace, prop, value) {
  if (value) {
    return update(node, {
      [namespace]: updateOrCreate(node[namespace], {
        [prop]: value
      })
    });
  }
  return update(node, {
    [namespace]: omit(node[namespace], prop)
  });
}
function setNodeNamespacedProp(dom, node, namespace, prop, value) {
  return update(dom, {
    nodes: update(dom.nodes, {
      [node.id]: update(dom.nodes[node.id], {
        [namespace]: updateOrCreate(dom.nodes[node.id][namespace], {
          [prop]: value
        })
      })
    })
  });
}
function setNodeParent(dom, node, parentId, parentProp, parentIndex) {
  if (!parentIndex) {
    const parent = getNode(dom, parentId);
    const children = getChildNodes(dom, parent)[parentProp] ?? [];
    const lastIndex = children.length > 0 ? children[children.length - 1].parentIndex : null;
    parentIndex = createFractionalIndex(lastIndex, null);
  }
  return update(dom, {
    nodes: update(dom.nodes, {
      [node.id]: update(node, {
        parentId,
        parentProp,
        parentIndex
      })
    })
  });
}
function addNode(dom, newNode, parent, parentProp, parentIndex) {
  if (newNode.parentId) {
    throw new Error(`Node "${newNode.id}" is already attached to a parent`);
  }
  const existingNames = getExistingNamesForChildren(dom, parent, parentProp);
  if (existingNames.has(newNode.name)) {
    newNode = {
      ...newNode,
      name: proposeName(newNode.name, existingNames)
    };
  }
  return setNodeParent(dom, newNode, parent.id, parentProp, parentIndex);
}
function removeNode(dom, nodeId) {
  const node = getNode(dom, nodeId);
  const parent = getParent(dom, node);
  invariant(parent, `Node: "${node.id}" can't be removed`);
  const descendantIds = getDescendants(dom, node).map(({ id }) => id);
  return update(dom, {
    nodes: omit(dom.nodes, node.id, ...descendantIds)
  });
}
function addFragment(dom, fragment, parentId, parentProp, parentIndex) {
  const parent = getNode(dom, parentId);
  const existingNames = getExistingNamesForChildren(dom, parent, parentProp);
  let combinedDom = {
    ...dom,
    nodes: {
      ...dom.nodes,
      ...mapValues(fragment.nodes, (node) => {
        return existingNames.has(node.name) ? { ...node, name: proposeName(node.name, existingNames) } : node;
      })
    }
  };
  const fragmentRoot = getNode(combinedDom, fragment.root);
  combinedDom = setNodeParent(combinedDom, fragmentRoot, parentId, parentProp, parentIndex);
  return combinedDom;
}
var RENDERTREE_NODES = ["app", "page", "element", "query", "mutation", "theme"];
var frontendNodes = new Set(RENDERTREE_NODES);
function createRenderTreeNode(node) {
  if (!frontendNodes.has(node.type)) {
    return null;
  }
  if (isQuery(node) || isMutation(node)) {
    if (node.attributes.query) {
      node = setNamespacedProp(node, "attributes", "query", null);
    }
  }
  return node;
}
function createRenderTree(dom) {
  return {
    ...dom,
    nodes: mapProperties(dom.nodes, ([id, node]) => {
      const rendernode = createRenderTreeNode(node);
      return rendernode ? [id, rendernode] : null;
    })
  };
}
function getPageByName(dom, name) {
  const rootNode = getApp(dom);
  const { pages = [] } = getChildNodes(dom, rootNode);
  return pages.find((page) => page.name === name) ?? null;
}
function getQueryByName(dom, page, name) {
  const { queries = [] } = getChildNodes(dom, page);
  return queries.find((query) => query.name === name) ?? null;
}
function applyDiff(dom, diff) {
  let result = dom;
  for (const node of diff.set) {
    result = update(result, {
      nodes: update(result.nodes, {
        [node.id]: node
      })
    });
  }
  for (const id of diff.unset) {
    result = update(result, {
      nodes: omit(result.nodes, id)
    });
  }
  return result;
}
function findEnvBindings(obj) {
  if (Array.isArray(obj)) {
    return obj.flatMap((item) => findEnvBindings(item));
  }
  if (obj && typeof obj === "object") {
    try {
      return [envBindingSchema.parse(obj)];
    } catch {
      return Object.values(obj).flatMap((value) => findEnvBindings(value));
    }
  }
  return [];
}
function getRequiredEnvVars(dom) {
  const allVars = Object.values(dom.nodes).flatMap((node) => findEnvBindings(node)).map((binding) => binding.$$env);
  return new Set(allVars);
}
function getPageDisplayName(node) {
  return node.attributes.displayName || guessTitle(node.name);
}
function getPageTitle(node) {
  return node.attributes.title || getPageDisplayName(node);
}

// src/runtime/toolpadComponents/index.tsx
var PAGE_ROW_COMPONENT_ID = "PageRow";
var PAGE_COLUMN_COMPONENT_ID = "PageColumn";
function getElementNodeComponentId(elementNode) {
  return elementNode.attributes.component;
}
function isPageRow(elementNode) {
  return getElementNodeComponentId(elementNode) === PAGE_ROW_COMPONENT_ID;
}
function isPageColumn(elementNode) {
  return getElementNodeComponentId(elementNode) === PAGE_COLUMN_COMPONENT_ID;
}
function isPageLayoutComponent(elementNode) {
  return isPageRow(elementNode) || isPageColumn(elementNode);
}

export {
  API_VERSION,
  applicationSchema,
  pageSchema,
  themeSchema,
  compareFractionalIndex,
  createId,
  createSecret,
  getMaybeNode,
  getNode,
  isPage,
  assertIsPage,
  isElement,
  assertIsElement,
  isQuery,
  isMutation,
  getRoot,
  getApp,
  getChildNodes,
  createNode,
  createFragment,
  createDom,
  createElement,
  setNodeNamespacedProp,
  addNode,
  removeNode,
  addFragment,
  createRenderTree,
  getPageByName,
  getQueryByName,
  applyDiff,
  getRequiredEnvVars,
  getPageDisplayName,
  getPageTitle,
  PAGE_ROW_COMPONENT_ID,
  PAGE_COLUMN_COMPONENT_ID,
  getElementNodeComponentId,
  isPageRow,
  isPageLayoutComponent
};
//# sourceMappingURL=chunk-ZUJ4UAKT.mjs.map