'use strict';

const api = require('@btst/stack/plugins/api');
const z = require('zod');
const schemaConverter = require('../../../../../ui/src/lib/schema-converter.cjs');
const db = require('../db.cjs');
const schemas = require('../schemas.cjs');
const utils = require('../utils.cjs');
const getters = require('./getters.cjs');
const mutations = require('./mutations.cjs');
const queryKeyDefs = require('./query-key-defs.cjs');
const utils$1 = require('../../utils.cjs');

async function syncContentTypes(adapter, config) {
  for (const ct of config.contentTypes) {
    const jsonSchema = JSON.stringify(schemaConverter.zodToFormSchema(ct.schema));
    const existing = await adapter.findOne({
      model: "contentType",
      where: [{ field: "slug", value: ct.slug, operator: "eq" }]
    });
    if (existing) {
      await adapter.update({
        model: "contentType",
        where: [{ field: "id", value: existing.id, operator: "eq" }],
        update: {
          name: ct.name,
          description: ct.description ?? null,
          jsonSchema,
          fieldConfig: null,
          // No longer used in version 2
          autoFormVersion: 2,
          updatedAt: /* @__PURE__ */ new Date()
        }
      });
    } else {
      try {
        await adapter.create({
          model: "contentType",
          data: {
            name: ct.name,
            slug: ct.slug,
            description: ct.description ?? null,
            jsonSchema,
            fieldConfig: null,
            // No longer used in version 2
            autoFormVersion: 2,
            createdAt: /* @__PURE__ */ new Date(),
            updatedAt: /* @__PURE__ */ new Date()
          }
        });
      } catch (err) {
        const nowExists = await adapter.findOne({
          model: "contentType",
          where: [{ field: "slug", value: ct.slug, operator: "eq" }]
        });
        if (nowExists) {
          continue;
        }
        const message = err instanceof Error ? err.message : "Unknown database error";
        throw new Error(
          `Failed to create content type "${ct.slug}": ${message}`
        );
      }
    }
  }
}
function getContentTypeZodSchema(contentType) {
  const jsonSchema = JSON.parse(contentType.jsonSchema);
  return schemaConverter.formSchemaToZod(jsonSchema);
}
function extractRelationFields(contentType) {
  const jsonSchema = JSON.parse(
    contentType.jsonSchema
  );
  const properties = jsonSchema.properties || {};
  const relationFields = {};
  for (const [fieldName, fieldSchema] of Object.entries(properties)) {
    if (fieldSchema.fieldType === "relation" && fieldSchema.relation) {
      relationFields[fieldName] = fieldSchema.relation;
    }
  }
  return relationFields;
}
function isNewRelationValue(value) {
  return typeof value === "object" && value !== null && "_new" in value && value._new === true && "data" in value;
}
function isExistingRelationValue(value) {
  return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string";
}
async function processRelationsInData(adapter, contentType, data, getContentTypeFn) {
  const relationFields = extractRelationFields(contentType);
  const processedData = { ...data };
  const relationIds = {};
  for (const [fieldName, relationConfig] of Object.entries(relationFields)) {
    if (!(fieldName in data)) {
      continue;
    }
    const fieldValue = data[fieldName];
    if (!fieldValue) {
      relationIds[fieldName] = [];
      continue;
    }
    const targetContentType = await getContentTypeFn(relationConfig.targetType);
    if (!targetContentType) {
      throw new Error(
        `Target content type "${relationConfig.targetType}" not found for relation field "${fieldName}"`
      );
    }
    const ids = [];
    if (relationConfig.type === "belongsTo") {
      const value = fieldValue;
      if (isNewRelationValue(value)) {
        const newItem = await createRelatedItem(
          adapter,
          targetContentType,
          value.data
        );
        ids.push(newItem.id);
        processedData[fieldName] = { id: newItem.id };
      } else if (isExistingRelationValue(value)) {
        ids.push(value.id);
      }
    } else {
      const values = Array.isArray(fieldValue) ? fieldValue : [];
      const processedValues = [];
      for (const value of values) {
        if (isNewRelationValue(value)) {
          const newItem = await createRelatedItem(
            adapter,
            targetContentType,
            value.data
          );
          ids.push(newItem.id);
          processedValues.push({ id: newItem.id });
        } else if (isExistingRelationValue(value)) {
          ids.push(value.id);
          processedValues.push({ id: value.id });
        }
      }
      processedData[fieldName] = processedValues;
    }
    relationIds[fieldName] = ids;
  }
  return { processedData, relationIds };
}
async function createRelatedItem(adapter, targetContentType, data) {
  const slug = utils.slugify(
    data.slug || data.name || data.title || `item-${Date.now()}`
  );
  const zodSchema = getContentTypeZodSchema(targetContentType);
  const validation = zodSchema.safeParse(data);
  if (!validation.success) {
    throw new Error(
      `Validation failed for new ${targetContentType.slug}: ${JSON.stringify(validation.error.issues)}`
    );
  }
  const existing = await adapter.findOne({
    model: "contentItem",
    where: [
      {
        field: "contentTypeId",
        value: targetContentType.id,
        operator: "eq"
      },
      { field: "slug", value: slug, operator: "eq" }
    ]
  });
  if (existing) {
    return existing;
  }
  const item = await adapter.create({
    model: "contentItem",
    data: {
      contentTypeId: targetContentType.id,
      slug,
      data: JSON.stringify(validation.data),
      createdAt: /* @__PURE__ */ new Date(),
      updatedAt: /* @__PURE__ */ new Date()
    }
  });
  return item;
}
async function syncRelations(adapter, sourceId, relationIds) {
  for (const [fieldName, targetIds] of Object.entries(relationIds)) {
    await adapter.delete({
      model: "contentRelation",
      where: [
        { field: "sourceId", value: sourceId, operator: "eq" },
        { field: "fieldName", value: fieldName, operator: "eq" }
      ]
    });
    for (const targetId of targetIds) {
      await adapter.create({
        model: "contentRelation",
        data: {
          sourceId,
          targetId,
          fieldName,
          createdAt: /* @__PURE__ */ new Date()
        }
      });
    }
  }
}
async function populateRelations(adapter, item) {
  const relations = {};
  const contentRelations = await adapter.findMany({
    model: "contentRelation",
    where: [{ field: "sourceId", value: item.id, operator: "eq" }]
  });
  const relationsByField = {};
  for (const rel of contentRelations) {
    if (!relationsByField[rel.fieldName]) {
      relationsByField[rel.fieldName] = [];
    }
    relationsByField[rel.fieldName].push(rel.targetId);
  }
  for (const [fieldName, targetIds] of Object.entries(relationsByField)) {
    if (targetIds.length === 0) {
      relations[fieldName] = [];
      continue;
    }
    const relatedItems = [];
    for (const targetId of targetIds) {
      const relatedItem = await adapter.findOne({
        model: "contentItem",
        where: [{ field: "id", value: targetId, operator: "eq" }],
        join: { contentType: true }
      });
      if (relatedItem) {
        relatedItems.push(getters.serializeContentItemWithType(relatedItem));
      }
    }
    relations[fieldName] = relatedItems;
  }
  return relations;
}
const cmsBackendPlugin = (config) => {
  let syncPromise = null;
  const ensureSynced = (adapter) => {
    if (!syncPromise) {
      syncPromise = syncContentTypes(adapter, config).catch((err) => {
        syncPromise = null;
        throw err;
      });
    }
    return syncPromise;
  };
  const getContentTypesWithCounts = async (adapter) => {
    const contentTypes = await getters.getAllContentTypes(adapter);
    return Promise.all(
      contentTypes.map(async (ct) => {
        const count = await adapter.count({
          model: "contentItem",
          where: [
            { field: "contentTypeId", value: ct.id, operator: "eq" }
          ]
        });
        return { ...ct, itemCount: count };
      })
    );
  };
  const createCMSPrefetchForRoute = (adapter) => {
    return async function prefetchForRoute(key, qc, params) {
      await ensureSynced(adapter);
      switch (key) {
        case "dashboard":
        case "newContent": {
          const typesWithCounts = await getContentTypesWithCounts(adapter);
          qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), typesWithCounts);
          break;
        }
        case "contentList": {
          const typeSlug = params?.typeSlug ?? "";
          const [contentTypes, contentItems] = await Promise.all([
            getContentTypesWithCounts(adapter),
            getters.getAllContentItems(adapter, typeSlug, { limit: 20, offset: 0 })
          ]);
          qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
          qc.setQueryData(
            queryKeyDefs.CMS_QUERY_KEYS.contentList({ typeSlug, limit: 20, offset: 0 }),
            {
              pages: [
                {
                  items: contentItems.items,
                  total: contentItems.total,
                  limit: contentItems.limit ?? 20,
                  offset: contentItems.offset ?? 0
                }
              ],
              pageParams: [0]
            }
          );
          break;
        }
        case "editContent": {
          const typeSlug = params?.typeSlug ?? "";
          const id = params?.id ?? "";
          const [contentTypes, item] = await Promise.all([
            getContentTypesWithCounts(adapter),
            id ? getters.getContentItemById(adapter, id) : Promise.resolve(null)
          ]);
          qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.typesList(), contentTypes);
          if (id) {
            qc.setQueryData(queryKeyDefs.CMS_QUERY_KEYS.contentDetail(typeSlug, id), item);
          }
          break;
        }
      }
    };
  };
  return api.defineBackendPlugin({
    name: "cms",
    dbPlugin: db.cmsSchema,
    api: (adapter) => ({
      getAllContentTypes: async () => {
        await ensureSynced(adapter);
        return getters.getAllContentTypes(adapter);
      },
      getAllContentItems: async (contentTypeSlug, params) => {
        await ensureSynced(adapter);
        return getters.getAllContentItems(adapter, contentTypeSlug, params);
      },
      getContentItemBySlug: async (contentTypeSlug, slug) => {
        await ensureSynced(adapter);
        return getters.getContentItemBySlug(adapter, contentTypeSlug, slug);
      },
      getContentItemById: async (id) => {
        await ensureSynced(adapter);
        return getters.getContentItemById(adapter, id);
      },
      prefetchForRoute: createCMSPrefetchForRoute(adapter),
      // Mutations
      createContentItem: async (typeSlug, input) => {
        await ensureSynced(adapter);
        return mutations.createCMSContentItem(adapter, typeSlug, input);
      }
    }),
    routes: (adapter) => {
      const getContentType = async (slug) => {
        await ensureSynced(adapter);
        return adapter.findOne({
          model: "contentType",
          where: [{ field: "slug", value: slug, operator: "eq" }]
        });
      };
      const createContext = (typeSlug, headers) => ({
        typeSlug,
        headers
      });
      const listContentTypes = api.createEndpoint(
        "/content-types",
        { method: "GET" },
        async (ctx) => {
          await ensureSynced(adapter);
          const contentTypes = await adapter.findMany({
            model: "contentType",
            sortBy: { field: "name", direction: "asc" }
          });
          const typesWithCounts = await Promise.all(
            contentTypes.map(async (ct) => {
              const items = await adapter.findMany({
                model: "contentItem",
                where: [
                  {
                    field: "contentTypeId",
                    value: ct.id,
                    operator: "eq"
                  }
                ]
              });
              return {
                ...getters.serializeContentType(ct),
                itemCount: items.length
              };
            })
          );
          return typesWithCounts;
        }
      );
      const getContentTypeBySlug = api.createEndpoint(
        "/content-types/:slug",
        {
          method: "GET",
          params: z.z.object({ slug: z.z.string() })
        },
        async (ctx) => {
          const { slug } = ctx.params;
          const contentType = await getContentType(slug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          return getters.serializeContentType(contentType);
        }
      );
      const listContentItems = api.createEndpoint(
        "/content/:typeSlug",
        {
          method: "GET",
          params: z.z.object({ typeSlug: z.z.string() }),
          query: schemas.listContentQuerySchema
        },
        async (ctx) => {
          const { typeSlug } = ctx.params;
          const { slug, limit, offset } = ctx.query;
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          return getters.getAllContentItems(adapter, typeSlug, { slug, limit, offset });
        }
      );
      const getContentItem = api.createEndpoint(
        "/content/:typeSlug/:id",
        {
          method: "GET",
          params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
        },
        async (ctx) => {
          const { typeSlug, id } = ctx.params;
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const item = await adapter.findOne({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }],
            join: { contentType: true }
          });
          if (!item || item.contentTypeId !== contentType.id) {
            throw ctx.error(404, { message: "Content item not found" });
          }
          return getters.serializeContentItemWithType(item);
        }
      );
      const createContentItem = api.createEndpoint(
        "/content/:typeSlug",
        {
          method: "POST",
          params: z.z.object({ typeSlug: z.z.string() }),
          body: z.z.object({
            slug: z.z.string().min(1),
            // Use passthrough object instead of z.record(z.unknown()) due to Zod v4 bug
            data: z.z.object({}).passthrough()
          })
        },
        async (ctx) => {
          const { typeSlug } = ctx.params;
          const { slug: rawSlug, data } = ctx.body;
          const context = createContext(typeSlug, ctx.headers);
          const slug = utils.slugify(rawSlug);
          if (!slug) {
            throw ctx.error(400, {
              message: "Invalid slug: must contain at least one alphanumeric character"
            });
          }
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const { processedData: dataWithResolvedRelations, relationIds } = await processRelationsInData(
            adapter,
            contentType,
            data,
            getContentType
          );
          const zodSchema = getContentTypeZodSchema(contentType);
          const validation = zodSchema.safeParse(dataWithResolvedRelations);
          if (!validation.success) {
            throw ctx.error(400, {
              message: "Validation failed",
              errors: validation.error.issues
            });
          }
          const existing = await adapter.findOne({
            model: "contentItem",
            where: [
              {
                field: "contentTypeId",
                value: contentType.id,
                operator: "eq"
              },
              { field: "slug", value: slug, operator: "eq" }
            ]
          });
          if (existing) {
            throw ctx.error(409, {
              message: "Content item with this slug already exists"
            });
          }
          const processedData = validation.data;
          if (config.hooks?.onBeforeCreate) {
            await utils$1.runHookWithShim(
              () => config.hooks.onBeforeCreate(processedData, context),
              ctx.error,
              "Create operation denied"
            );
          }
          const item = await adapter.create({
            model: "contentItem",
            data: {
              contentTypeId: contentType.id,
              slug,
              data: JSON.stringify(processedData),
              createdAt: /* @__PURE__ */ new Date(),
              updatedAt: /* @__PURE__ */ new Date()
            }
          });
          await syncRelations(adapter, item.id, relationIds);
          const serialized = getters.serializeContentItem(item);
          if (config.hooks?.onAfterCreate) {
            await config.hooks.onAfterCreate(serialized, context);
          }
          return {
            ...serialized,
            parsedData: processedData
          };
        }
      );
      const updateContentItem = api.createEndpoint(
        "/content/:typeSlug/:id",
        {
          method: "PUT",
          params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() }),
          body: z.z.object({
            slug: z.z.string().min(1).optional(),
            // Use passthrough object instead of z.record(z.unknown()) due to Zod v4 bug
            data: z.z.object({}).passthrough().optional()
          })
        },
        async (ctx) => {
          const { typeSlug, id } = ctx.params;
          const { slug: rawSlug, data } = ctx.body;
          const context = createContext(typeSlug, ctx.headers);
          const slug = rawSlug ? utils.slugify(rawSlug) : void 0;
          if (rawSlug && !slug) {
            throw ctx.error(400, {
              message: "Invalid slug: must contain at least one alphanumeric character"
            });
          }
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const existing = await adapter.findOne({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }]
          });
          if (!existing || existing.contentTypeId !== contentType.id) {
            throw ctx.error(404, { message: "Content item not found" });
          }
          if (slug && slug !== existing.slug) {
            const duplicate = await adapter.findOne({
              model: "contentItem",
              where: [
                {
                  field: "contentTypeId",
                  value: contentType.id,
                  operator: "eq"
                },
                { field: "slug", value: slug, operator: "eq" }
              ]
            });
            if (duplicate) {
              throw ctx.error(409, {
                message: "Content item with this slug already exists"
              });
            }
          }
          let dataWithResolvedRelations;
          let relationIds;
          if (data) {
            const result = await processRelationsInData(
              adapter,
              contentType,
              data,
              getContentType
            );
            dataWithResolvedRelations = result.processedData;
            relationIds = result.relationIds;
          }
          let validatedData = dataWithResolvedRelations;
          if (dataWithResolvedRelations) {
            const existingData = existing.data ? JSON.parse(existing.data) : {};
            const mergedData = {
              ...existingData,
              ...dataWithResolvedRelations
            };
            const zodSchema = getContentTypeZodSchema(contentType);
            const validation = zodSchema.safeParse(mergedData);
            if (!validation.success) {
              throw ctx.error(400, {
                message: "Validation failed",
                errors: validation.error.issues
              });
            }
            validatedData = validation.data;
          }
          const processedData = validatedData;
          if (config.hooks?.onBeforeUpdate && validatedData) {
            await utils$1.runHookWithShim(
              () => config.hooks.onBeforeUpdate(id, validatedData, context),
              ctx.error,
              "Update operation denied"
            );
          }
          if (relationIds) {
            await syncRelations(adapter, id, relationIds);
          }
          const updateData = {
            updatedAt: /* @__PURE__ */ new Date()
          };
          if (slug) updateData.slug = slug;
          if (processedData) updateData.data = JSON.stringify(processedData);
          await adapter.update({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }],
            update: updateData
          });
          const updated = await adapter.findOne({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }],
            join: { contentType: true }
          });
          if (!updated) {
            throw ctx.error(500, { message: "Failed to fetch updated item" });
          }
          const serialized = getters.serializeContentItem(updated);
          if (config.hooks?.onAfterUpdate) {
            await config.hooks.onAfterUpdate(serialized, context);
          }
          return getters.serializeContentItemWithType(updated);
        }
      );
      const deleteContentItem = api.createEndpoint(
        "/content/:typeSlug/:id",
        {
          method: "DELETE",
          params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
        },
        async (ctx) => {
          const { typeSlug, id } = ctx.params;
          const context = createContext(typeSlug, ctx.headers);
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const existing = await adapter.findOne({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }]
          });
          if (!existing || existing.contentTypeId !== contentType.id) {
            throw ctx.error(404, { message: "Content item not found" });
          }
          if (config.hooks?.onBeforeDelete) {
            await utils$1.runHookWithShim(
              () => config.hooks.onBeforeDelete(id, context),
              ctx.error,
              "Delete operation denied"
            );
          }
          await adapter.delete({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }]
          });
          if (config.hooks?.onAfterDelete) {
            await config.hooks.onAfterDelete(id, context);
          }
          return { success: true };
        }
      );
      const getContentItemPopulated = api.createEndpoint(
        "/content/:typeSlug/:id/populated",
        {
          method: "GET",
          params: z.z.object({ typeSlug: z.z.string(), id: z.z.string() })
        },
        async (ctx) => {
          const { typeSlug, id } = ctx.params;
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const item = await adapter.findOne({
            model: "contentItem",
            where: [{ field: "id", value: id, operator: "eq" }],
            join: { contentType: true }
          });
          if (!item || item.contentTypeId !== contentType.id) {
            throw ctx.error(404, { message: "Content item not found" });
          }
          const _relations = await populateRelations(adapter, item);
          return {
            ...getters.serializeContentItemWithType(item),
            _relations
          };
        }
      );
      const listContentByRelation = api.createEndpoint(
        "/content/:typeSlug/by-relation",
        {
          method: "GET",
          params: z.z.object({ typeSlug: z.z.string() }),
          query: z.z.object({
            field: z.z.string(),
            targetId: z.z.string(),
            limit: z.z.coerce.number().min(1).max(100).optional().default(20),
            offset: z.z.coerce.number().min(0).optional().default(0)
          })
        },
        async (ctx) => {
          const { typeSlug } = ctx.params;
          const { field, targetId, limit, offset } = ctx.query;
          const contentType = await getContentType(typeSlug);
          if (!contentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const contentRelations = await adapter.findMany({
            model: "contentRelation",
            where: [
              { field: "targetId", value: targetId, operator: "eq" },
              { field: "fieldName", value: field, operator: "eq" }
            ]
          });
          const sourceIds = [
            ...new Set(contentRelations.map((r) => r.sourceId))
          ];
          if (sourceIds.length === 0) {
            return {
              items: [],
              total: 0,
              limit,
              offset
            };
          }
          const allItems = [];
          for (const sourceId of sourceIds) {
            const item = await adapter.findOne({
              model: "contentItem",
              where: [
                { field: "id", value: sourceId, operator: "eq" },
                {
                  field: "contentTypeId",
                  value: contentType.id,
                  operator: "eq"
                }
              ],
              join: { contentType: true }
            });
            if (item) {
              allItems.push(item);
            }
          }
          allItems.sort(
            (a, b) => b.createdAt.getTime() - a.createdAt.getTime()
          );
          const total = allItems.length;
          const paginatedItems = allItems.slice(offset, offset + limit);
          return {
            items: paginatedItems.map(getters.serializeContentItemWithType),
            total,
            limit,
            offset
          };
        }
      );
      const getInverseRelations = api.createEndpoint(
        "/content-types/:slug/inverse-relations",
        {
          method: "GET",
          params: z.z.object({ slug: z.z.string() }),
          query: z.z.object({
            itemId: z.z.string().optional()
          })
        },
        async (ctx) => {
          const { slug } = ctx.params;
          const { itemId } = ctx.query;
          await ensureSynced(adapter);
          const targetContentType = await getContentType(slug);
          if (!targetContentType) {
            throw ctx.error(404, { message: "Content type not found" });
          }
          const allContentTypes = await adapter.findMany({
            model: "contentType"
          });
          const inverseRelations = [];
          for (const contentType of allContentTypes) {
            const relationFields = extractRelationFields(contentType);
            for (const [fieldName, relationConfig] of Object.entries(
              relationFields
            )) {
              if (relationConfig.type === "belongsTo" && relationConfig.targetType === slug) {
                let count = 0;
                if (itemId) {
                  const relations = await adapter.findMany({
                    model: "contentRelation",
                    where: [
                      {
                        field: "targetId",
                        value: itemId,
                        operator: "eq"
                      },
                      {
                        field: "fieldName",
                        value: fieldName,
                        operator: "eq"
                      }
                    ]
                  });
                  const itemIds = relations.map((r) => r.sourceId);
                  for (const sourceId of itemIds) {
                    const item = await adapter.findOne({
                      model: "contentItem",
                      where: [
                        {
                          field: "id",
                          value: sourceId,
                          operator: "eq"
                        },
                        {
                          field: "contentTypeId",
                          value: contentType.id,
                          operator: "eq"
                        }
                      ]
                    });
                    if (item) count++;
                  }
                }
                inverseRelations.push({
                  sourceType: contentType.slug,
                  sourceTypeName: contentType.name,
                  fieldName,
                  count
                });
              }
            }
          }
          return { inverseRelations };
        }
      );
      const listInverseRelationItems = api.createEndpoint(
        "/content-types/:slug/inverse-relations/:sourceType",
        {
          method: "GET",
          params: z.z.object({
            slug: z.z.string(),
            sourceType: z.z.string()
          }),
          query: z.z.object({
            itemId: z.z.string(),
            fieldName: z.z.string(),
            limit: z.z.coerce.number().min(1).max(100).optional().default(20),
            offset: z.z.coerce.number().min(0).optional().default(0)
          })
        },
        async (ctx) => {
          const { slug, sourceType } = ctx.params;
          const { itemId, fieldName, limit, offset } = ctx.query;
          await ensureSynced(adapter);
          const targetContentType = await getContentType(slug);
          if (!targetContentType) {
            throw ctx.error(404, { message: "Target content type not found" });
          }
          const sourceContentType = await getContentType(sourceType);
          if (!sourceContentType) {
            throw ctx.error(404, { message: "Source content type not found" });
          }
          const relations = await adapter.findMany({
            model: "contentRelation",
            where: [
              { field: "targetId", value: itemId, operator: "eq" },
              { field: "fieldName", value: fieldName, operator: "eq" }
            ]
          });
          const sourceIds = [...new Set(relations.map((r) => r.sourceId))];
          const allItems = [];
          for (const sourceId of sourceIds) {
            const item = await adapter.findOne({
              model: "contentItem",
              where: [
                { field: "id", value: sourceId, operator: "eq" },
                {
                  field: "contentTypeId",
                  value: sourceContentType.id,
                  operator: "eq"
                }
              ],
              join: { contentType: true }
            });
            if (item) {
              allItems.push(item);
            }
          }
          allItems.sort(
            (a, b) => b.createdAt.getTime() - a.createdAt.getTime()
          );
          const total = allItems.length;
          const paginatedItems = allItems.slice(offset, offset + limit);
          return {
            items: paginatedItems.map(getters.serializeContentItemWithType),
            total,
            limit,
            offset
          };
        }
      );
      return {
        listContentTypes,
        getContentTypeBySlug,
        listContentItems,
        getContentItem,
        createContentItem,
        updateContentItem,
        deleteContentItem,
        getContentItemPopulated,
        listContentByRelation,
        getInverseRelations,
        listInverseRelationItems
      };
    }
  });
};

exports.cmsBackendPlugin = cmsBackendPlugin;
