import { n as validateSubmission, t as toRecord } from "./validate-wrOGX2lR.js";
import { checkAntiSpam } from "./antispam.js";
import { and, desc, eq, lt } from "drizzle-orm";
import { boolean, index, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
//#region src/schema.ts
const formSubmissions = pgTable("form_submissions", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	formId: text("form_id").notNull(),
	data: jsonb("data").$type().notNull(),
	meta: jsonb("meta").$type(),
	spam: boolean("spam").notNull().default(false),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [index("form_submissions_form_created_idx").on(t.formId, desc(t.createdAt))]);
const formsSchema = { formSubmissions };
/**
* Pure DDL helper — returns CREATE TABLE + index SQL the host applies once (per-statement-split
* on neon-http). Column names + types match the drizzle table above exactly.
*/
function formsTableSql(table = "form_submissions") {
	return `
CREATE TABLE IF NOT EXISTS ${table} (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  form_id text NOT NULL,
  data jsonb NOT NULL,
  meta jsonb,
  spam boolean NOT NULL DEFAULT false,
  created_at timestamptz(3) NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS form_submissions_form_created_idx ON ${table} (form_id, created_at DESC);
`.trim();
}
//#endregion
//#region src/store.ts
/**
* Cap on each host-supplied `meta` string (ip/userAgent/ref). These are persisted, attacker-
* influenced header values (User-Agent, Referer) — the module's "bounds every STORED value"
* doctrine applies to them too, not just declared fields. 2k is generous for any real header.
*/
const META_MAX = 2e3;
const capMeta = (v) => v === void 0 ? void 0 : v.length > META_MAX ? v.slice(0, META_MAX) : v;
function rowToSubmission(row) {
	return {
		id: row.id,
		formId: row.formId,
		data: row.data,
		createdAt: row.createdAt.toISOString(),
		meta: row.meta ?? void 0,
		spam: row.spam
	};
}
/**
* Reference adapter over the `db` type-contract. All reads/writes are SCOPED by formId (IDOR floor):
* a submission read MUST be authorized by the host before calling list/get; the store never
* exposes a cross-form global read. Cursor = the last row's createdAt ISO (keyset by created_at desc).
*/
function createDbSubmissionStore(db) {
	return {
		async record(s) {
			return rowToSubmission((await db.insert(formSubmissions).values({
				formId: s.formId,
				data: s.data,
				meta: s.meta,
				spam: s.spam ?? false
			}).returning())[0]);
		},
		async get(formId, id) {
			const rows = await db.select().from(formSubmissions).where(and(eq(formSubmissions.id, id), eq(formSubmissions.formId, formId))).limit(1);
			return rows[0] ? rowToSubmission(rows[0]) : null;
		},
		async list(formId, opts) {
			const limit = Math.min(Math.max(opts?.limit ?? 50, 1), 200);
			const conds = [eq(formSubmissions.formId, formId)];
			if (!opts?.includeSpam) conds.push(eq(formSubmissions.spam, false));
			if (opts?.cursor) conds.push(lt(formSubmissions.createdAt, new Date(opts.cursor)));
			const rows = await db.select().from(formSubmissions).where(and(...conds)).orderBy(desc(formSubmissions.createdAt)).limit(limit + 1);
			const items = rows.slice(0, limit).map(rowToSubmission);
			return {
				items,
				nextCursor: rows.length > limit ? items[items.length - 1].createdAt : void 0
			};
		},
		async delete(formId, id) {
			await db.delete(formSubmissions).where(and(eq(formSubmissions.id, id), eq(formSubmissions.formId, formId)));
		}
	};
}
/**
* The one call a host endpoint wires: validate → anti-spam (sets the spam flag) → store.record.
* Validation failure => Result error and NOTHING is stored. A spam verdict is stored with
* spam=true (quarantine for review) — FLAG not drop (data-loss floor).
*/
async function recordSubmission(args) {
	const valid = validateSubmission(args.form, args.raw);
	if (!valid.ok) return valid;
	const verdict = await checkAntiSpam(args.form, toRecord(args.raw), {
		renderToken: args.antispam.renderToken,
		secret: args.antispam.secret,
		now: args.antispam.now,
		challengeToken: args.antispam.challengeToken,
		ip: args.antispam.ip
	});
	return {
		ok: true,
		value: await args.store.record({
			formId: args.form.id,
			data: valid.value,
			meta: {
				ip: capMeta(args.antispam.ip),
				userAgent: capMeta(args.antispam.userAgent),
				ref: capMeta(args.antispam.ref)
			},
			spam: !verdict.ok
		})
	};
}
//#endregion
export { createDbSubmissionStore, formSubmissions, formsSchema, formsTableSql, recordSubmission };
