/**
 * Room invites on the shared `recflare` D1 database — one row per game invite a player
 * sends another ("come join me in this room"), as `POST /invite` on the `match` worker
 * creates them.
 *
 * The invite that reaches the invitee is a live notification, not a row: `match` pushes a
 * `MessageReceived` frame the moment the invite is created, and the client renders the join
 * prompt straight off it. This table exists so the invite has an ID OF ITS OWN —
 * `RoomInviteId`, which the create response hands back — and so an invite can be looked up
 * or expired after the fact rather than vanishing with the socket frame.
 *
 * The `match` worker owns this schema/migration
 * (`apps/match/migrations/0001_room_invite.sql`, applied under its own `migrations_table`
 * so it doesn't clash with the other workers' migrations that share the database).
 * `ROOM_INVITE_SCHEMA_DDL` mirrors that migration so tests can build the table directly.
 */

/** Schema DDL (mirror of apps/match/migrations/0001_room_invite.sql). */
export const ROOM_INVITE_SCHEMA_DDL: string[] = [
	// `room_invite_id` is AUTOINCREMENT rather than a bare rowid alias: the id is handed to
	// the client, and expiring old invites deletes rows, so a reused id would point a
	// client's stale invite at somebody else's.
	//
	// `room_id` is nullable because the invite is: the caller names a room INSTANCE, and one
	// that has already died (or was never real) leaves the invite with nothing to resolve —
	// `match` sends it anyway, with a null RoomId, so the row records the same thing.
	//
	// `created_at` is epoch SECONDS, like `presence.expires_at` on the same database and for
	// the same reason: the sweep that will expire these compares it against `Date.now()/1000`
	// in SQL, and an integer compare needs no parsing. Indexed for that sweep.
	`CREATE TABLE IF NOT EXISTS room_invite (
		room_invite_id INTEGER PRIMARY KEY AUTOINCREMENT,
		from_player_id INTEGER NOT NULL,
		to_player_id INTEGER NOT NULL,
		room_id INTEGER,
		created_at INTEGER NOT NULL
	)`,
	`CREATE INDEX IF NOT EXISTS idx_room_invite_created ON room_invite (created_at)`,
]

/**
 * One invite as the client reads it — PascalCase, and the whole of what `POST /invite`
 * answers with.
 */
export interface RoomInvite {
	RoomInviteId: number
	FromPlayerId: number
	ToPlayerId: number
	RoomId: number | null
}

interface RoomInviteRow {
	room_invite_id: number
	from_player_id: number
	to_player_id: number
	room_id: number | null
}

const SELECT_COLUMNS = `room_invite_id, from_player_id, to_player_id, room_id`

const nowSeconds = () => Math.floor(Date.now() / 1000)

/**
 * Record an invite from `fromPlayerId` to `toPlayerId` for a room, returning it as the
 * client reads it back. `roomId` is null when the caller's room instance didn't resolve.
 *
 * The caller still has to deliver the invite (the `MessageReceived` frame); this only
 * mints the row and its id.
 */
export async function createRoomInvite(
	db: D1Database,
	fromPlayerId: number,
	toPlayerId: number,
	roomId: number | null
): Promise<RoomInvite | null> {
	const row = await db
		.prepare(
			`INSERT INTO room_invite (from_player_id, to_player_id, room_id, created_at)
			 VALUES (?1, ?2, ?3, ?4)
			 RETURNING ${SELECT_COLUMNS}`
		)
		.bind(fromPlayerId, toPlayerId, roomId, nowSeconds())
		.first<RoomInviteRow>()

	if (!row) return null
	return {
		RoomInviteId: row.room_invite_id,
		FromPlayerId: row.from_player_id,
		ToPlayerId: row.to_player_id,
		RoomId: row.room_id,
	}
}
