import { defineBackendPlugin, createEndpoint } from '@btst/stack/plugins/api';
import { z } from 'zod';
import { formSchemaToZod } from '../../../../../ui/src/lib/schema-converter.mjs';
import { formBuilderSchema } from '../db.mjs';
import { listFormsQuerySchema, createFormSchema, updateFormSchema, listSubmissionsQuerySchema } from '../schemas.mjs';
import { extractUserAgent, extractIpAddress, slugify } from '../utils.mjs';

function serializeForm(form) {
  return {
    id: form.id,
    name: form.name,
    slug: form.slug,
    description: form.description,
    schema: form.schema,
    successMessage: form.successMessage,
    redirectUrl: form.redirectUrl,
    status: form.status,
    createdBy: form.createdBy,
    createdAt: form.createdAt.toISOString(),
    updatedAt: form.updatedAt.toISOString()
  };
}
function serializeFormSubmission(submission) {
  return {
    ...submission,
    submittedAt: submission.submittedAt.toISOString()
  };
}
function serializeFormSubmissionWithData(submission) {
  return {
    ...serializeFormSubmission(submission),
    parsedData: JSON.parse(submission.data),
    form: submission.form ? serializeForm(submission.form) : void 0
  };
}
const formBuilderBackendPlugin = (config = {}) => defineBackendPlugin({
  name: "form-builder",
  dbPlugin: formBuilderSchema,
  routes: (adapter) => {
    const createContext = (headers) => ({
      headers,
      ipAddress: extractIpAddress(headers),
      userAgent: extractUserAgent(headers)
    });
    const createSubmissionContext = (formSlug, formId, headers) => ({
      ...createContext(headers),
      formSlug,
      formId
    });
    const listForms = createEndpoint(
      "/forms",
      {
        method: "GET",
        query: listFormsQuerySchema
      },
      async (ctx) => {
        const { status, limit, offset } = ctx.query;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeListForms) {
          const canList = await config.hooks.onBeforeListForms(context);
          if (!canList) {
            throw ctx.error(403, { message: "Access denied" });
          }
        }
        const whereConditions = [];
        if (status) {
          whereConditions.push({
            field: "status",
            value: status,
            operator: "eq"
          });
        }
        const allForms = await adapter.findMany({
          model: "form",
          where: whereConditions.length > 0 ? whereConditions : void 0
        });
        const total = allForms.length;
        const forms = await adapter.findMany({
          model: "form",
          where: whereConditions.length > 0 ? whereConditions : void 0,
          limit,
          offset,
          sortBy: { field: "createdAt", direction: "desc" }
        });
        return {
          items: forms.map(serializeForm),
          total,
          limit,
          offset
        };
      }
    );
    const getFormBySlug = createEndpoint(
      "/forms/:slug",
      {
        method: "GET",
        params: z.object({ slug: z.string() })
      },
      async (ctx) => {
        const { slug } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetForm) {
          const canGet = await config.hooks.onBeforeGetForm(slug, context);
          if (!canGet) {
            throw ctx.error(403, { message: "Access denied" });
          }
        }
        const form = await adapter.findOne({
          model: "form",
          where: [{ field: "slug", value: slug, operator: "eq" }]
        });
        if (!form) {
          throw ctx.error(404, { message: "Form not found" });
        }
        return serializeForm(form);
      }
    );
    const getFormById = createEndpoint(
      "/forms/id/:id",
      {
        method: "GET",
        params: z.object({ id: z.string() })
      },
      async (ctx) => {
        const { id } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetForm) {
          const canGet = await config.hooks.onBeforeGetForm(id, context);
          if (!canGet) {
            throw ctx.error(403, { message: "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 serializeForm(form);
      }
    );
    const createForm = createEndpoint(
      "/forms",
      {
        method: "POST",
        body: createFormSchema
      },
      async (ctx) => {
        const body = ctx.body;
        const context = createContext(ctx.headers);
        const slug = 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 result = await config.hooks.onBeforeFormCreated(
            formInput,
            context
          );
          if (result === false) {
            throw ctx.error(403, { message: "Create operation denied" });
          }
          if (result && typeof result === "object") {
            formInput = result;
          }
        }
        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 = serializeForm(form);
        if (config.hooks?.onAfterFormCreated) {
          await config.hooks.onAfterFormCreated(serialized, context);
        }
        return serialized;
      }
    );
    const updateForm = createEndpoint(
      "/forms/:id",
      {
        method: "PUT",
        params: z.object({ id: z.string() }),
        body: 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 = 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 result = await config.hooks.onBeforeFormUpdated(
            id,
            updateInput,
            context
          );
          if (result === false) {
            throw ctx.error(403, { message: "Update operation denied" });
          }
          if (result && typeof result === "object") {
            updateInput = result;
          }
        }
        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 = serializeForm(updated);
        if (config.hooks?.onAfterFormUpdated) {
          await config.hooks.onAfterFormUpdated(serialized, context);
        }
        return serialized;
      }
    );
    const deleteForm = createEndpoint(
      "/forms/:id",
      {
        method: "DELETE",
        params: z.object({ id: 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) {
          const canDelete = await config.hooks.onBeforeFormDeleted(
            id,
            context
          );
          if (!canDelete) {
            throw ctx.error(403, { message: "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 = createEndpoint(
      "/forms/:slug/submit",
      {
        method: "POST",
        params: z.object({ slug: z.string() }),
        body: z.object({
          // Use passthrough object for dynamic form data
          data: 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 = 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) {
          try {
            const result = await config.hooks.onBeforeSubmission(
              slug,
              data,
              submissionContext
            );
            if (result === false) {
              throw ctx.error(400, { message: "Submission rejected" });
            }
            if (result && typeof result === "object") {
              finalData = result;
            }
          } catch (error) {
            if (config.hooks?.onSubmissionError) {
              await config.hooks.onSubmissionError(
                error,
                slug,
                data,
                submissionContext
              );
            }
            throw error;
          }
        }
        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 = serializeFormSubmission(submission);
        if (config.hooks?.onAfterSubmission) {
          await config.hooks.onAfterSubmission(
            serialized,
            serializeForm(form),
            submissionContext
          );
        }
        return {
          ...serialized,
          form: {
            successMessage: form.successMessage,
            redirectUrl: form.redirectUrl
          }
        };
      }
    );
    const listSubmissions = createEndpoint(
      "/forms/:formId/submissions",
      {
        method: "GET",
        params: z.object({ formId: z.string() }),
        query: 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) {
          const canList = await config.hooks.onBeforeListSubmissions(
            formId,
            context
          );
          if (!canList) {
            throw ctx.error(403, { message: "Access denied" });
          }
        }
        const allSubmissions = await adapter.findMany({
          model: "formSubmission",
          where: [
            { field: "formId", value: formId, operator: "eq" }
          ]
        });
        const total = allSubmissions.length;
        const submissions = await adapter.findMany({
          model: "formSubmission",
          where: [
            { field: "formId", value: formId, operator: "eq" }
          ],
          limit,
          offset,
          sortBy: { field: "submittedAt", direction: "desc" },
          join: { form: true }
        });
        return {
          items: submissions.map(serializeFormSubmissionWithData),
          total,
          limit,
          offset
        };
      }
    );
    const getSubmission = createEndpoint(
      "/forms/:formId/submissions/:subId",
      {
        method: "GET",
        params: z.object({ formId: z.string(), subId: z.string() })
      },
      async (ctx) => {
        const { formId, subId } = ctx.params;
        const context = createContext(ctx.headers);
        if (config.hooks?.onBeforeGetSubmission) {
          const canGet = await config.hooks.onBeforeGetSubmission(
            subId,
            context
          );
          if (!canGet) {
            throw ctx.error(403, { message: "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 serializeFormSubmissionWithData(submission);
      }
    );
    const deleteSubmission = createEndpoint(
      "/forms/:formId/submissions/:subId",
      {
        method: "DELETE",
        params: z.object({ formId: z.string(), subId: 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) {
          const canDelete = await config.hooks.onBeforeSubmissionDeleted(
            subId,
            context
          );
          if (!canDelete) {
            throw ctx.error(403, { message: "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
    };
  }
});

export { formBuilderBackendPlugin };
