'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 queryKeyDefs = require('./query-key-defs.cjs');
const utils$1 = require('../../utils.cjs');

function createFormBuilderPrefetchForRoute(adapter) {
  return async function prefetchForRoute(key, qc, params) {
    switch (key) {
      case "formList": {
        const result = await getters.getAllForms(adapter, { limit: 20, offset: 0 });
        qc.setQueryData(queryKeyDefs.FORM_QUERY_KEYS.formsList({ limit: 20, offset: 0 }), {
          pages: [
            {
              items: result.items,
              total: result.total,
              limit: result.limit ?? 20,
              offset: result.offset ?? 0
            }
          ],
          pageParams: [0]
        });
        break;
      }
      case "editForm": {
        const id = params?.id ?? "";
        if (id) {
          const form = await getters.getFormById(adapter, id);
          qc.setQueryData(queryKeyDefs.FORM_QUERY_KEYS.formById(id), form);
        }
        break;
      }
      case "submissions": {
        const id = params?.id ?? "";
        if (id) {
          const [form, submissionsResult] = await Promise.all([
            getters.getFormById(adapter, id),
            getters.getFormSubmissions(adapter, id, { limit: 20, offset: 0 })
          ]);
          qc.setQueryData(queryKeyDefs.FORM_QUERY_KEYS.formById(id), form);
          qc.setQueryData(
            queryKeyDefs.FORM_QUERY_KEYS.submissionsList({
              formId: id,
              limit: 20,
              offset: 0
            }),
            {
              pages: [
                {
                  items: submissionsResult.items,
                  total: submissionsResult.total,
                  limit: submissionsResult.limit ?? 20,
                  offset: submissionsResult.offset ?? 0
                }
              ],
              pageParams: [0]
            }
          );
        }
        break;
      }
    }
  };
}
const formBuilderBackendPlugin = (config = {}) => api.defineBackendPlugin({
  name: "form-builder",
  dbPlugin: db.formBuilderSchema,
  api: (adapter) => ({
    getAllForms: (params) => getters.getAllForms(adapter, params),
    getFormById: (id) => getters.getFormById(adapter, id),
    getFormBySlug: (slug) => getters.getFormBySlug(adapter, slug),
    getFormSubmissions: (formId, params) => getters.getFormSubmissions(adapter, formId, params),
    prefetchForRoute: createFormBuilderPrefetchForRoute(adapter)
  }),
  routes: (adapter) => {
    const createContext = (headers) => ({
      headers,
      ipAddress: utils.extractIpAddress(headers),
      userAgent: utils.extractUserAgent(headers)
    });
    const createSubmissionContext = (formSlug, formId, headers) => ({
      ...createContext(headers),
      formSlug,
      formId
    });
    const listForms = api.createEndpoint(
      "/forms",
      {
        method: "GET",
        query: schemas.listFormsQuerySchema
      },
      async (ctx) => {
        const { status, limit, offset } = ctx.query;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeListForms) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeListForms(context),
            ctx.error,
            "Access denied"
          );
        }
        return getters.getAllForms(adapter, { status, limit, offset });
      }
    );
    const getFormBySlug = api.createEndpoint(
      "/forms/:slug",
      {
        method: "GET",
        params: z.z.object({ slug: z.z.string() })
      },
      async (ctx) => {
        const { slug } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetForm) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeGetForm(slug, context),
            ctx.error,
            "Access denied"
          );
        }
        const form = await getters.getFormBySlug(adapter, slug);
        if (!form) {
          throw ctx.error(404, { message: "Form not found" });
        }
        return form;
      }
    );
    const getFormById = api.createEndpoint(
      "/forms/id/:id",
      {
        method: "GET",
        params: z.z.object({ id: z.z.string() })
      },
      async (ctx) => {
        const { id } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetForm) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeGetForm(id, context),
            ctx.error,
            "Access denied"
          );
        }
        const form = await adapter.findOne({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }]
        });
        if (!form) {
          throw ctx.error(404, { message: "Form not found" });
        }
        return getters.serializeForm(form);
      }
    );
    const createForm = api.createEndpoint(
      "/forms",
      {
        method: "POST",
        body: schemas.createFormSchema
      },
      async (ctx) => {
        const body = ctx.body;
        const context = createContext(ctx.headers);
        const slug = utils.slugify(body.slug);
        if (!slug) {
          throw ctx.error(400, {
            message: "Invalid slug: must contain at least one alphanumeric character"
          });
        }
        const existing = await adapter.findOne({
          model: "form",
          where: [{ field: "slug", value: slug, operator: "eq" }]
        });
        if (existing) {
          throw ctx.error(409, {
            message: "Form with this slug already exists"
          });
        }
        try {
          JSON.parse(body.schema);
        } catch {
          throw ctx.error(400, { message: "Invalid JSON Schema" });
        }
        let formInput = {
          name: body.name,
          slug,
          description: body.description,
          schema: body.schema,
          successMessage: body.successMessage,
          redirectUrl: body.redirectUrl || void 0,
          status: body.status
        };
        if (config.hooks?.onBeforeFormCreated) {
          const hookResult = await utils$1.runHookWithShim(
            () => config.hooks.onBeforeFormCreated(formInput, context),
            ctx.error,
            "Create operation denied"
          );
          if (hookResult && typeof hookResult === "object") {
            formInput = hookResult;
          }
        }
        const form = await adapter.create({
          model: "form",
          data: {
            name: formInput.name,
            slug: formInput.slug,
            description: formInput.description,
            schema: formInput.schema,
            successMessage: formInput.successMessage,
            redirectUrl: formInput.redirectUrl,
            status: formInput.status || "active",
            createdBy: formInput.createdBy,
            createdAt: /* @__PURE__ */ new Date(),
            updatedAt: /* @__PURE__ */ new Date()
          }
        });
        const serialized = getters.serializeForm(form);
        if (config.hooks?.onAfterFormCreated) {
          await config.hooks.onAfterFormCreated(serialized, context);
        }
        return serialized;
      }
    );
    const updateForm = api.createEndpoint(
      "/forms/:id",
      {
        method: "PUT",
        params: z.z.object({ id: z.z.string() }),
        body: schemas.updateFormSchema
      },
      async (ctx) => {
        const { id } = ctx.params;
        const body = ctx.body;
        const context = createContext(ctx.headers);
        const existing = await adapter.findOne({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }]
        });
        if (!existing) {
          throw ctx.error(404, { message: "Form not found" });
        }
        let slug;
        if (body.slug) {
          slug = utils.slugify(body.slug);
          if (!slug) {
            throw ctx.error(400, {
              message: "Invalid slug: must contain at least one alphanumeric character"
            });
          }
          if (slug !== existing.slug) {
            const duplicate = await adapter.findOne({
              model: "form",
              where: [
                { field: "slug", value: slug, operator: "eq" }
              ]
            });
            if (duplicate) {
              throw ctx.error(409, {
                message: "Form with this slug already exists"
              });
            }
          }
        }
        if (body.schema) {
          try {
            JSON.parse(body.schema);
          } catch {
            throw ctx.error(400, { message: "Invalid JSON Schema" });
          }
        }
        let updateInput = {
          name: body.name,
          slug,
          description: body.description,
          schema: body.schema,
          successMessage: body.successMessage,
          redirectUrl: body.redirectUrl,
          status: body.status
        };
        if (config.hooks?.onBeforeFormUpdated) {
          const hookResult = await utils$1.runHookWithShim(
            () => config.hooks.onBeforeFormUpdated(id, updateInput, context),
            ctx.error,
            "Update operation denied"
          );
          if (hookResult && typeof hookResult === "object") {
            updateInput = hookResult;
          }
        }
        const updateData = {
          updatedAt: /* @__PURE__ */ new Date()
        };
        if (updateInput.name) updateData.name = updateInput.name;
        if (updateInput.slug) updateData.slug = updateInput.slug;
        if (updateInput.description !== void 0)
          updateData.description = updateInput.description;
        if (updateInput.schema) updateData.schema = updateInput.schema;
        if (updateInput.successMessage !== void 0)
          updateData.successMessage = updateInput.successMessage;
        if (updateInput.redirectUrl !== void 0)
          updateData.redirectUrl = updateInput.redirectUrl;
        if (updateInput.status) updateData.status = updateInput.status;
        await adapter.update({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }],
          update: updateData
        });
        const updated = await adapter.findOne({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }]
        });
        if (!updated) {
          throw ctx.error(500, { message: "Failed to fetch updated form" });
        }
        const serialized = getters.serializeForm(updated);
        if (config.hooks?.onAfterFormUpdated) {
          await config.hooks.onAfterFormUpdated(serialized, context);
        }
        return serialized;
      }
    );
    const deleteForm = api.createEndpoint(
      "/forms/:id",
      {
        method: "DELETE",
        params: z.z.object({ id: z.z.string() })
      },
      async (ctx) => {
        const { id } = ctx.params;
        const context = createContext(ctx.headers);
        const existing = await adapter.findOne({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }]
        });
        if (!existing) {
          throw ctx.error(404, { message: "Form not found" });
        }
        if (config.hooks?.onBeforeFormDeleted) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeFormDeleted(id, context),
            ctx.error,
            "Delete operation denied"
          );
        }
        await adapter.delete({
          model: "formSubmission",
          where: [{ field: "formId", value: id, operator: "eq" }]
        });
        await adapter.delete({
          model: "form",
          where: [{ field: "id", value: id, operator: "eq" }]
        });
        if (config.hooks?.onAfterFormDeleted) {
          await config.hooks.onAfterFormDeleted(id, context);
        }
        return { success: true };
      }
    );
    const submitForm = api.createEndpoint(
      "/forms/:slug/submit",
      {
        method: "POST",
        params: z.z.object({ slug: z.z.string() }),
        body: z.z.object({
          // Use passthrough object for dynamic form data
          data: z.z.object({}).passthrough()
        })
      },
      async (ctx) => {
        const { slug } = ctx.params;
        const { data } = ctx.body;
        const baseContext = createContext(ctx.headers);
        const form = await adapter.findOne({
          model: "form",
          where: [{ field: "slug", value: slug, operator: "eq" }]
        });
        if (!form) {
          throw ctx.error(404, { message: "Form not found" });
        }
        if (form.status !== "active") {
          throw ctx.error(400, {
            message: "Form is not accepting submissions"
          });
        }
        const submissionContext = createSubmissionContext(
          slug,
          form.id,
          ctx.headers
        );
        try {
          const jsonSchema = JSON.parse(form.schema);
          const zodSchema = schemaConverter.formSchemaToZod(jsonSchema);
          const validation = zodSchema.safeParse(data);
          if (!validation.success) {
            throw ctx.error(400, {
              message: "Validation failed",
              errors: validation.error.issues
            });
          }
        } catch (error) {
          if (error && typeof error === "object" && "code" in error) {
            throw error;
          }
          throw ctx.error(400, { message: "Invalid form data" });
        }
        let finalData = data;
        if (config.hooks?.onBeforeSubmission) {
          let hookResult;
          let originalError;
          try {
            hookResult = await config.hooks.onBeforeSubmission(
              slug,
              data,
              submissionContext
            );
            if (hookResult === false) {
              originalError = new Error("Submission rejected");
            }
          } catch (e) {
            originalError = e instanceof Error ? e : new Error("Submission rejected");
          }
          if (originalError) {
            if (config.hooks?.onSubmissionError) {
              await config.hooks.onSubmissionError(
                originalError,
                slug,
                data,
                submissionContext
              );
            }
            throw ctx.error(400, { message: originalError.message });
          }
          if (hookResult && typeof hookResult === "object") {
            finalData = hookResult;
          }
        }
        const submission = await adapter.create({
          model: "formSubmission",
          data: {
            formId: form.id,
            data: JSON.stringify(finalData),
            submittedAt: /* @__PURE__ */ new Date(),
            ipAddress: baseContext.ipAddress,
            userAgent: baseContext.userAgent
          }
        });
        const serialized = getters.serializeFormSubmission(submission);
        if (config.hooks?.onAfterSubmission) {
          await config.hooks.onAfterSubmission(
            serialized,
            getters.serializeForm(form),
            submissionContext
          );
        }
        return {
          ...serialized,
          form: {
            successMessage: form.successMessage,
            redirectUrl: form.redirectUrl
          }
        };
      }
    );
    const listSubmissions = api.createEndpoint(
      "/forms/:formId/submissions",
      {
        method: "GET",
        params: z.z.object({ formId: z.z.string() }),
        query: schemas.listSubmissionsQuerySchema
      },
      async (ctx) => {
        const { formId } = ctx.params;
        const { limit, offset } = ctx.query;
        const context = createContext(ctx.headers);
        const form = await adapter.findOne({
          model: "form",
          where: [{ field: "id", value: formId, operator: "eq" }]
        });
        if (!form) {
          throw ctx.error(404, { message: "Form not found" });
        }
        if (config.hooks?.onBeforeListSubmissions) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeListSubmissions(formId, context),
            ctx.error,
            "Access denied"
          );
        }
        return getters.getFormSubmissions(adapter, formId, { limit, offset });
      }
    );
    const getSubmission = api.createEndpoint(
      "/forms/:formId/submissions/:subId",
      {
        method: "GET",
        params: z.z.object({ formId: z.z.string(), subId: z.z.string() })
      },
      async (ctx) => {
        const { formId, subId } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetSubmission) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeGetSubmission(subId, context),
            ctx.error,
            "Access denied"
          );
        }
        const submission = await adapter.findOne({
          model: "formSubmission",
          where: [{ field: "id", value: subId, operator: "eq" }],
          join: { form: true }
        });
        if (!submission || submission.formId !== formId) {
          throw ctx.error(404, { message: "Submission not found" });
        }
        return getters.serializeFormSubmissionWithData(submission);
      }
    );
    const deleteSubmission = api.createEndpoint(
      "/forms/:formId/submissions/:subId",
      {
        method: "DELETE",
        params: z.z.object({ formId: z.z.string(), subId: z.z.string() })
      },
      async (ctx) => {
        const { formId, subId } = ctx.params;
        const context = createContext(ctx.headers);
        const existing = await adapter.findOne({
          model: "formSubmission",
          where: [{ field: "id", value: subId, operator: "eq" }]
        });
        if (!existing || existing.formId !== formId) {
          throw ctx.error(404, { message: "Submission not found" });
        }
        if (config.hooks?.onBeforeSubmissionDeleted) {
          await utils$1.runHookWithShim(
            () => config.hooks.onBeforeSubmissionDeleted(subId, context),
            ctx.error,
            "Delete operation denied"
          );
        }
        await adapter.delete({
          model: "formSubmission",
          where: [{ field: "id", value: subId, operator: "eq" }]
        });
        if (config.hooks?.onAfterSubmissionDeleted) {
          await config.hooks.onAfterSubmissionDeleted(subId, context);
        }
        return { success: true };
      }
    );
    return {
      listForms,
      getFormBySlug,
      getFormById,
      createForm,
      updateForm,
      deleteForm,
      submitForm,
      listSubmissions,
      getSubmission,
      deleteSubmission
    };
  }
});

exports.formBuilderBackendPlugin = formBuilderBackendPlugin;
