// ../content-model/src/templates/types.ts
var TEMPLATE_BINDING_TRANSFORMS = {
  RICHTEXT_FIRST_PARAGRAPH: "richtext.firstParagraph",
  DATE_FORMAT_SHORT: "date.formatShort",
  DATE_FORMAT_LONG: "date.formatLong"
};

// ../content-model/src/templates/bindingTypeCompatibility.ts
var CONTENT_TO_BLOCK_COMPATIBILITY = {
  // Simple text fields
  text: ["text", "slug", "url"],
  // Multi-line text
  textarea: ["text"],
  // Numeric fields can bind to number inputs or text display fields.
  number: ["number", "text"],
  // Date fields can bind to date/datetime inputs or text display fields.
  date: ["date", "datetime", "text"],
  // URL fields can bind to URL inputs or text display fields.
  url: ["url", "text"],
  // Rich text (requires exact match or transform)
  richText: ["richText"],
  // Media fields
  media: ["media"],
  // Boolean fields
  boolean: ["boolean"],
  // Select dropdowns (can map to text or select)
  select: ["text", "select"],
  // Reference fields (object or link)
  reference: ["reference", "link"],
  // Link fields (CTA links, etc.)
  link: ["link", "reference"],
  // Button fields (styled links with variants)
  button: ["link"],
  // Group fields (nested structure - not directly bindable)
  group: [],
  // Repeater fields (list structure - not directly bindable)
  repeater: [],
  // Datetime fields can map to datetime/date inputs or text display fields.
  datetime: ["datetime", "date", "text"]
};
var META_FIELDS = {
  title: "text",
  slug: "text",
  status: "text",
  publishedAt: "date",
  createdAt: "date",
  updatedAt: "date"
};
var META_FIELD_IDS = new Set(Object.keys(META_FIELDS));
function resolveMetaFieldType(path) {
  return isMetaFieldPath(path) ? META_FIELDS[path] : null;
}
function isMetaFieldPath(path) {
  return path in META_FIELDS;
}
var META_FIELD_COMPATIBILITY = {
  // Text meta fields — title/slug carry the alias targets so editors can bind
  // them where the generic 'text' → ['text','slug','url'] would allow.
  title: ["text", "slug", "url"],
  slug: ["text", "slug", "url"],
  // Enum/status fields
  status: ["text", "select"],
  // Date meta fields
  publishedAt: ["text", "date", "datetime"],
  createdAt: ["text", "date", "datetime"],
  updatedAt: ["text", "date", "datetime"]
};
var NESTED_PATH_COMPATIBILITY = {
  // richText nested paths
  "richText.doc": ["richText"],
  "richText.html": ["text"],
  "richText.plain": ["text"],
  // media nested paths
  "media.url": ["text", "url"],
  "media.alt": ["text"]
};
function isCompatibleBinding(sourceType, targetType) {
  const compatibleTypes = CONTENT_TO_BLOCK_COMPATIBILITY[sourceType];
  if (compatibleTypes) {
    return compatibleTypes.includes(targetType);
  }
  return false;
}
function isCompatibleMetaBinding(metaFieldName, targetType) {
  if (!isMetaFieldPath(metaFieldName)) return false;
  const compatibleTypes = META_FIELD_COMPATIBILITY[metaFieldName];
  return compatibleTypes.includes(targetType);
}
function isCompatibleNestedPath(parentType, nestedSuffix, targetType) {
  const pathKey = `${parentType}.${nestedSuffix}`;
  const compatibleTypes = NESTED_PATH_COMPATIBILITY[pathKey];
  if (compatibleTypes) {
    return compatibleTypes.includes(targetType);
  }
  return false;
}

// ../content-model/src/templates/fieldPaths.ts
var META_FIELD_LABELS = {
  title: "Title",
  slug: "Slug",
  status: "Status",
  publishedAt: "Published Date",
  createdAt: "Created Date",
  updatedAt: "Updated Date"
};
function getAvailableFieldPaths(schema) {
  const paths = [];
  for (const [path, type] of Object.entries(META_FIELDS)) {
    paths.push({
      path,
      label: META_FIELD_LABELS[path],
      type,
      category: "meta"
    });
  }
  paths.push(...contentFieldPaths(schema.fields ?? []));
  return paths;
}
function contentFieldPaths(fields, parentPath = "", parentLabel = "") {
  const paths = [];
  for (const field of fields) {
    const basePath = parentPath ? `${parentPath}.${field.id}` : field.id;
    const fieldLabel = field.label ?? field.id;
    const baseLabel = parentLabel ? `${parentLabel} / ${fieldLabel}` : fieldLabel;
    if (field.type === "group") {
      paths.push(...contentFieldPaths(nestedFields(field), basePath, baseLabel));
      continue;
    }
    if (field.type === "repeater") {
      continue;
    }
    if (field.type === "richText") {
      paths.push(
        {
          path: basePath,
          label: baseLabel,
          type: "richText",
          category: "content"
        },
        {
          path: `${basePath}.doc`,
          label: `${baseLabel} (Document)`,
          type: "richText.doc",
          category: "content"
        },
        {
          path: `${basePath}.html`,
          label: `${baseLabel} (HTML)`,
          type: "richText.html",
          category: "content"
        },
        {
          path: `${basePath}.plain`,
          label: `${baseLabel} (Plain Text)`,
          type: "richText.plain",
          category: "content"
        }
      );
      continue;
    }
    if (field.type === "media") {
      paths.push(
        {
          path: basePath,
          label: baseLabel,
          type: "media",
          category: "content"
        },
        {
          path: `${basePath}.url`,
          label: `${baseLabel} (URL)`,
          type: "media.url",
          category: "content"
        },
        {
          path: `${basePath}.alt`,
          label: `${baseLabel} (Alt Text)`,
          type: "media.alt",
          category: "content"
        }
      );
      continue;
    }
    paths.push({
      path: basePath,
      label: baseLabel,
      type: field.type,
      category: "content"
    });
  }
  return paths;
}
function nestedFields(field) {
  const record = field;
  return record.fields ?? record.schema?.fields ?? [];
}
function getFieldTypeFromPath(schema, path) {
  const paths = getAvailableFieldPaths(schema);
  const match = paths.find((p) => p.path === path);
  return match?.type ?? null;
}

// ../content-model/src/templates/bindingValidation.ts
var TRANSFORM_IO = {
  [TEMPLATE_BINDING_TRANSFORMS.RICHTEXT_FIRST_PARAGRAPH]: {
    acceptsInputTypes: ["richText"],
    outputType: "text"
  },
  [TEMPLATE_BINDING_TRANSFORMS.DATE_FORMAT_SHORT]: {
    acceptsInputTypes: ["date", "datetime"],
    outputType: "text"
  },
  [TEMPLATE_BINDING_TRANSFORMS.DATE_FORMAT_LONG]: {
    acceptsInputTypes: ["date", "datetime"],
    outputType: "text"
  }
};
function getTransformIO(name) {
  return name in TRANSFORM_IO ? TRANSFORM_IO[name] : void 0;
}
function resolveContentPathType(schema, path) {
  return getFieldTypeFromPath(schema, path);
}
function resolveMetaPathType(path) {
  return resolveMetaFieldType(path);
}
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isBindingDescriptor(value) {
  if (!isRecord(value)) return false;
  const source = value.source;
  return source === "field" || source === "meta" || source === "literal" || source === "transform" || source === "fallback";
}
function isRootContextBinding(value) {
  if (!isRecord(value)) return false;
  const bind = value.$bind;
  if (!isRecord(bind)) return false;
  return typeof bind.from === "string" && bind.from.startsWith("$root.");
}
function isSourceCompatibleWithTarget(sourceType, targetType) {
  if (sourceType.includes(".")) {
    const [parent, suffix] = sourceType.split(".", 2);
    if (!parent || !suffix) return false;
    return isCompatibleNestedPath(parent, suffix, targetType);
  }
  return isCompatibleBinding(sourceType, targetType);
}
function resolveFieldSource(path, context) {
  const type = resolveContentPathType(context.schema, path);
  if (type === null) {
    return {
      kind: "invalidPath",
      error: {
        kind: "invalidFieldPath",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        path
      }
    };
  }
  return { kind: "ok", type, path };
}
function resolveMetaSource(path, context) {
  const type = resolveMetaPathType(path);
  if (type === null) {
    return {
      kind: "invalidPath",
      error: {
        kind: "invalidMetaPath",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        path
      }
    };
  }
  return { kind: "ok", type, path };
}
function checkDirectCompatibility(resolved, context, isMetaSource) {
  if (isMetaSource) {
    if (isCompatibleMetaBinding(resolved.path, context.target.type)) return null;
    return {
      kind: "typeIncompatible",
      blockIdentifier: context.blockIdentifier,
      fieldKey: context.fieldKey,
      sourceType: `meta.${resolved.path}`,
      sourcePath: resolved.path,
      targetType: context.target.type,
      via: "direct"
    };
  }
  if (isSourceCompatibleWithTarget(resolved.type, context.target.type)) return null;
  return {
    kind: "typeIncompatible",
    blockIdentifier: context.blockIdentifier,
    fieldKey: context.fieldKey,
    sourceType: resolved.type,
    sourcePath: resolved.path,
    targetType: context.target.type,
    via: "direct"
  };
}
function validateBinding(binding, context) {
  if (typeof binding === "string") {
    const resolved = resolveFieldSource(binding, context);
    if (resolved.kind === "invalidPath") return [resolved.error];
    const mismatch = checkDirectCompatibility(resolved, context, false);
    return mismatch ? [mismatch] : [];
  }
  if (!isRecord(binding)) {
    return [
      {
        kind: "typeIncompatible",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        sourceType: typeof binding,
        targetType: context.target.type,
        via: "direct"
      }
    ];
  }
  if (isBindingDescriptor(binding)) {
    return validateDescriptor(binding, context);
  }
  const errors = [];
  for (const [childKey, child] of Object.entries(binding)) {
    if (child === null || child === void 0) continue;
    const nestedKey = `${context.fieldKey}.${childKey}`;
    const nestedTarget = context.targetByKey.get(nestedKey);
    if (!nestedTarget) {
      errors.push({
        kind: "invalidBindingKey",
        blockIdentifier: context.blockIdentifier,
        blockKind: context.blockKind,
        fieldKey: nestedKey
      });
      continue;
    }
    errors.push(
      ...validateBinding(child, {
        ...context,
        fieldKey: nestedKey,
        target: nestedTarget
      })
    );
  }
  return errors;
}
function validateDescriptor(binding, context) {
  switch (binding.source) {
    case "field": {
      const resolved = resolveFieldSource(binding.path, context);
      if (resolved.kind === "invalidPath") return [resolved.error];
      const mismatch = checkDirectCompatibility(resolved, context, false);
      return mismatch ? [mismatch] : [];
    }
    case "meta": {
      const resolved = resolveMetaSource(binding.path, context);
      if (resolved.kind === "invalidPath") return [resolved.error];
      const mismatch = checkDirectCompatibility(resolved, context, true);
      return mismatch ? [mismatch] : [];
    }
    case "literal":
      return [];
    case "transform":
      return validateTransform(binding, context);
    case "fallback":
      return readFallbackOptions(binding).flatMap(
        (option) => validateBinding(option, { ...context }).map(
          (err) => (
            // Reclassify direct-type errors inside a fallback so the caller
            // can render "one of these options doesn't satisfy the target".
            err.kind === "typeIncompatible" && err.via === "direct" ? { ...err, via: "fallbackOption" } : err
          )
        )
      );
    default: {
      const _exhaustive = binding;
      return _exhaustive;
    }
  }
}
function validateTransform(binding, context) {
  const io = getTransformIO(binding.transform);
  if (!io) {
    return [
      {
        kind: "unknownTransform",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        transform: binding.transform
      }
    ];
  }
  const inputDescriptor = resolveTransformInput(binding);
  if (inputDescriptor === null) {
    return [
      {
        kind: "typeIncompatible",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        sourceType: "transform(no input)",
        targetType: context.target.type,
        via: "transformOutput"
      }
    ];
  }
  const resolved = inputDescriptor.source === "field" ? resolveFieldSource(inputDescriptor.path, context) : inputDescriptor.source === "meta" ? resolveMetaSource(inputDescriptor.path, context) : null;
  if (resolved && resolved.kind === "invalidPath") {
    return [resolved.error];
  }
  const errors = [];
  if (resolved && resolved.kind === "ok") {
    const inputType = resolved.type;
    if (!io.acceptsInputTypes.includes(inputType)) {
      errors.push({
        kind: "transformInputIncompatible",
        blockIdentifier: context.blockIdentifier,
        fieldKey: context.fieldKey,
        transform: binding.transform,
        inputType,
        acceptedTypes: io.acceptsInputTypes
      });
    }
  }
  if (!isCompatibleBinding(io.outputType, context.target.type)) {
    errors.push({
      kind: "typeIncompatible",
      blockIdentifier: context.blockIdentifier,
      fieldKey: context.fieldKey,
      sourceType: io.outputType,
      targetType: context.target.type,
      via: "transformOutput"
    });
  }
  return errors;
}
function readFallbackOptions(binding) {
  if (Array.isArray(binding.options)) return binding.options;
  const legacy = binding.sources;
  return Array.isArray(legacy) ? legacy : [];
}
function resolveTransformInput(binding) {
  if (binding.input !== void 0) {
    if (typeof binding.input === "string") {
      return { source: "field", path: binding.input };
    }
    return binding.input;
  }
  if (typeof binding.path === "string") {
    return { source: "field", path: binding.path };
  }
  return null;
}
function validateBlockBindings(block, schema, manifest) {
  const blockIdentifier = block.identifier ?? block.blockKind;
  if (block.scope === "template") return [];
  const errors = [];
  const bindings = block.bindings ?? {};
  const targetByKey = new Map(
    manifest.fields.map((target) => [target.id, target])
  );
  for (const [fieldKey, binding] of Object.entries(bindings)) {
    if (binding === null || binding === void 0) continue;
    if (isRootContextBinding(binding)) continue;
    const target = targetByKey.get(fieldKey);
    if (!target) {
      errors.push({
        kind: "invalidBindingKey",
        blockIdentifier,
        blockKind: block.blockKind,
        fieldKey
      });
      continue;
    }
    errors.push(
      ...validateBinding(binding, {
        blockIdentifier,
        fieldKey,
        target,
        schema,
        targetByKey,
        blockKind: block.blockKind
      })
    );
  }
  for (const target of manifest.fields) {
    if (!target.required) continue;
    const value = bindings[target.id];
    if (value === void 0 || value === null) {
      errors.push({
        kind: "requiredBindingMissing",
        blockIdentifier,
        blockKind: block.blockKind,
        fieldKey: target.id
      });
    }
  }
  return errors;
}
function validateTemplateBindings(template, schema, getManifest) {
  const errors = [];
  for (const [index, block] of template.blocks.entries()) {
    const blockIdentifier = block.identifier ?? `${block.blockKind}#${index}`;
    if (block.scope === "template") continue;
    const manifest = getManifest(block.blockKind);
    if (!manifest) {
      errors.push({
        kind: "unknownBlockKind",
        blockIdentifier,
        blockKind: block.blockKind
      });
      continue;
    }
    errors.push(
      ...validateBlockBindings(
        { ...block, identifier: blockIdentifier },
        schema,
        manifest
      )
    );
  }
  return errors;
}
function matchBindingError(error, handlers) {
  const handler = handlers[error.kind];
  return handler(error);
}
function bindingValidationErrorMessage(error) {
  return matchBindingError(error, {
    unknownBlockKind: (e) => `Unknown block kind "${e.blockKind}"`,
    invalidBindingKey: (e) => `Binding key "${e.fieldKey}" is not a field of block "${e.blockKind}"`,
    invalidFieldPath: (e) => `Field path "${e.path}" does not exist in the content type`,
    invalidMetaPath: (e) => `Meta path "${e.path}" is not a known entry meta field`,
    typeIncompatible: (e) => {
      const ctx = e.via === "transformOutput" ? " (transform output)" : e.via === "fallbackOption" ? " (fallback option)" : "";
      const pathSuffix = e.sourcePath ? ` (path "${e.sourcePath}")` : "";
      return `Source type "${e.sourceType}" is not compatible with target field type "${e.targetType}"${pathSuffix}${ctx}`;
    },
    unknownTransform: (e) => `Unknown transform "${e.transform}"`,
    transformInputIncompatible: (e) => `Transform "${e.transform}" does not accept input type "${e.inputType}" (accepts: ${e.acceptedTypes.join(", ")})`,
    requiredBindingMissing: (e) => `Required field "${e.fieldKey}" of block "${e.blockKind}" has no binding`
  });
}

// ../content-model/src/templates/manifestAdapter.ts
var FIELD_TYPE_CLASSIFICATION = {
  // Leaves — bindable, type maps cleanly into BlockFieldType.
  text: "leaf",
  number: "leaf",
  slug: "leaf",
  url: "leaf",
  richText: "leaf",
  media: "leaf",
  boolean: "leaf",
  select: "leaf",
  reference: "leaf",
  link: "leaf",
  date: "leaf",
  datetime: "leaf",
  // Composites — walked; their leaves emerge as dot-notated targets.
  group: "composite",
  modal: "composite",
  tabGroup: "composite",
  // Repeaters: monomorphic has a single inner shape we could walk, but the
  // leaves would be bindings to repeated items, not to a fixed path. Skip.
  repeater: "opaque",
  // Opaque types — dashboard-only widgets without an underlying bindable leaf.
  presetOrCustom: "opaque",
  contentTypeSelect: "opaque",
  entryPicker: "opaque",
  time: "opaque"
};
function classify(type) {
  return FIELD_TYPE_CLASSIFICATION[type] ?? "unknown";
}
function leafToBlockFieldType(type) {
  return classify(type) === "leaf" ? type : null;
}
function collectLeaves(fields, prefix, out) {
  if (!fields) return;
  for (const field of fields) {
    const id = prefix ? `${prefix}.${field.id}` : field.id;
    const cls = classify(field.type);
    if (cls === "leaf") {
      out.push({
        id,
        type: field.type,
        required: Boolean(field.required)
      });
      continue;
    }
    if (cls === "composite") {
      const groupChildren = field.schema?.fields;
      if (groupChildren) collectLeaves(groupChildren, id, out);
      for (const tab of field.tabs ?? []) {
        if (tab.fields) collectLeaves(tab.fields, id, out);
      }
    }
    void leafToBlockFieldType;
  }
}
function manifestToValidationShape(manifest) {
  const fields = [];
  collectLeaves(manifest.fields, "", fields);
  return { id: manifest.name, fields };
}

// src/config/template-validation.ts
function toContentTypeSchema(fields) {
  return {
    version: 1,
    fields: fields.map(toContentFieldConfig),
    labels: { singular: "Entry", plural: "Entries" }
  };
}
function toContentFieldConfig(field) {
  const fields = nestedSdkFields(field);
  return {
    ...field,
    id: field.id,
    type: toContentFieldType(field.type),
    ...fields ? { fields: fields.map(toContentFieldConfig) } : {}
  };
}
function nestedSdkFields(field) {
  switch (field.type) {
    case "group":
    case "modal":
      return field.schema.fields;
    case "repeater":
      return field.schema?.fields;
    case "tabGroup":
      return field.tabs.flatMap((tab) => tab.fields);
    default:
      return void 0;
  }
}
function toContentFieldType(type) {
  switch (type) {
    case "text":
    case "richText":
    case "media":
    case "boolean":
    case "number":
    case "date":
    case "datetime":
    case "url":
    case "link":
    case "select":
    case "reference":
    case "repeater":
    case "group":
      return type;
    case "slug":
    case "time":
    case "presetOrCustom":
    case "contentTypeSelect":
    case "entryPicker":
      return "text";
    case "modal":
    case "tabGroup":
      return "group";
    default: {
      const _exhaustive = type;
      return _exhaustive;
    }
  }
}
function toCanonicalBlock(block) {
  return {
    blockKind: block.kind,
    scope: block.scope,
    identifier: block.identifier,
    content: block.content,
    bindings: block.bindings ? normalizeBindingsRecord(block.bindings) : void 0
  };
}
function normalizeBindingsRecord(bindings) {
  return Object.fromEntries(
    Object.entries(bindings).map(([key, value]) => [key, normalizeBinding(value)])
  );
}
function normalizeBinding(binding) {
  switch (binding.source) {
    case "field":
    case "meta":
    case "literal":
      return binding;
    case "transform":
      return {
        source: "transform",
        transform: binding.transform,
        path: binding.path
      };
    case "fallback":
      return {
        source: "fallback",
        options: binding.sources.map(normalizeBinding)
      };
    default: {
      const _exhaustive = binding;
      return _exhaustive;
    }
  }
}
function validationErrorType(error) {
  switch (error.kind) {
    case "unknownBlockKind":
      return "invalid_block_kind";
    case "invalidBindingKey":
      return "invalid_binding_key";
    case "invalidFieldPath":
      return "invalid_field_path";
    case "invalidMetaPath":
      return "invalid_meta_path";
    case "typeIncompatible":
      return "type_incompatible";
    case "unknownTransform":
      return "unknown_transform";
    case "transformInputIncompatible":
      return "transform_input_incompatible";
    case "requiredBindingMissing":
      return "required_binding_missing";
    default: {
      const _exhaustive = error;
      return _exhaustive;
    }
  }
}
function toTemplateValidationError(error) {
  return {
    blockIdentifier: error.blockIdentifier,
    ..."fieldKey" in error ? { fieldKey: error.fieldKey } : {},
    message: bindingValidationErrorMessage(error),
    type: validationErrorType(error)
  };
}
function validateTemplateBindings2(blocks, contentTypeFields, getBlockManifest) {
  const schema = toContentTypeSchema(contentTypeFields);
  const template = {
    templateKey: "sdk-template",
    name: "SDK template",
    blocks: blocks.map(toCanonicalBlock)
  };
  const getManifest = (kind) => {
    const manifest = getBlockManifest(kind);
    if (!manifest) return void 0;
    return manifestToValidationShape({
      name: kind,
      fields: (manifest.fields ?? []).map(toAdaptableManifestField)
    });
  };
  return validateTemplateBindings(template, schema, getManifest).map(
    toTemplateValidationError
  );
}
function toAdaptableManifestField(field) {
  return {
    ...field,
    id: field.id,
    // Older SDK tests and very old custom manifests only carried field ids.
    // Treat those as text targets so canonical validation can still check
    // binding-key/path correctness instead of considering every field opaque.
    type: field.type ?? "text"
  };
}
function formatValidationErrors(errors) {
  return errors.map((error) => {
    const location = error.fieldKey ? `[${error.blockIdentifier}.${error.fieldKey}]` : `[${error.blockIdentifier}]`;
    return `${location} ${error.message}`;
  });
}
export {
  formatValidationErrors,
  validateTemplateBindings2 as validateTemplateBindings
};
