import { and, desc, eq, or } from "drizzle-orm";
import { drizzle } from "drizzle-orm/mysql2";
import { nanoid } from "nanoid";
import { createTicketToken } from "./ticket-token";
import { complimentaryInvites, events, InsertEvent, orders, paymentSubmissions, ticketTypes, tickets, InsertUser, users } from "../drizzle/schema";
import { ENV } from "./_core/env";
import { deliverIssuedTicketEmail, getTicketEmailTrigger } from "./ticket-email-flow";
import { sendTicketEmail } from "./emailjs";
import { sendTicketSms } from "./mnotify";
import { makeAvailableEventSlug } from "./event-slug";

let _db: ReturnType<typeof drizzle> | null = null;
type SmsStatus = "not_sent" | "sending" | "sent" | "failed";

export async function deliverIssuedTicketSms(input: { orderId: number; attendeeName: string; attendeePhone: string; eventTitle: string; quantity: number; qrToken: string }, updateStatus: (status: SmsStatus) => Promise<void>, sender: typeof sendTicketSms = sendTicketSms) {
  await updateStatus("sending");
  try {
    const sent = await sender({ recipient: input.attendeePhone, attendeeName: input.attendeeName, eventTitle: input.eventTitle, orderId: input.orderId, quantity: input.quantity, qrToken: input.qrToken });
    await updateStatus(sent ? "sent" : "failed");
    return sent;
  } catch (error) {
    console.warn("[SMS] Ticket confirmation failed:", error);
    await updateStatus("failed");
    return false;
  }
}
export function setDbForTests(db: ReturnType<typeof drizzle> | null) { _db = db; }
export async function getDb() { if (!_db && process.env.DATABASE_URL) { try { _db = drizzle(process.env.DATABASE_URL); } catch (error) { console.warn("[Database] Failed to connect:", error); _db = null; } } return _db; }
export async function upsertUser(user: InsertUser): Promise<void> { if (!user.openId) throw new Error("User openId is required for upsert"); const db = await getDb(); if (!db) return; const values: InsertUser = { openId: user.openId }; const updateSet: Record<string, unknown> = {}; (['name', 'email', 'loginMethod'] as const).forEach((field) => { if (user[field] !== undefined) { values[field] = user[field] ?? null; updateSet[field] = user[field] ?? null; } }); if (user.lastSignedIn !== undefined) { values.lastSignedIn = user.lastSignedIn; updateSet.lastSignedIn = user.lastSignedIn; } if (user.role !== undefined) { values.role = user.role; updateSet.role = user.role; } else if (user.openId === ENV.ownerOpenId) { values.role = 'admin'; updateSet.role = 'admin'; } if (!values.lastSignedIn) values.lastSignedIn = new Date(); if (!Object.keys(updateSet).length) updateSet.lastSignedIn = new Date(); await db.insert(users).values(values).onDuplicateKeyUpdate({ set: updateSet }); }
export async function getUserByOpenId(openId: string) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(users).where(eq(users.openId, openId)).limit(1); return result[0]; }
export async function listPublishedEvents() { const db = await getDb(); if (!db) return []; return db.select().from(events).where(eq(events.status, 'published')).orderBy(events.startsAt); }
export async function listOrganizerEvents(organizerId: number) { const db = await getDb(); if (!db) return []; return db.select().from(events).where(eq(events.organizerId, organizerId)).orderBy(desc(events.createdAt)); }
export async function createEvent(event: InsertEvent) { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const result = await db.insert(events).values(event); return Number(result[0].insertId); }
export async function createEventWithTicket(event: InsertEvent, ticket: { name: string; description?: string; priceMinor: number; quantityLimit: number }, additionalTickets: Array<{ name: string; description?: string; priceMinor: number; quantityLimit: number }> = []) { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const existingSlug = (await db.select().from(events).where(eq(events.slug, event.slug)).limit(1))[0]; const eventToInsert = { ...event, slug: makeAvailableEventSlug(event.slug, Boolean(existingSlug), nanoid(6).toLowerCase()) }; const result = await db.insert(events).values(eventToInsert); const eventId = Number(result[0].insertId); const allTickets = [ticket, ...additionalTickets.filter((item) => item.name.trim())]; await db.insert(ticketTypes).values(allTickets.map((item) => ({ eventId, name: item.name, description: item.description ?? null, priceMinor: item.priceMinor, quantityLimit: item.quantityLimit, quantitySold: 0 }))); return eventId; }
export function canManageEvent(eventOrganizerId: number, actorId: number, actorRole: string) { return actorRole === "admin" || eventOrganizerId === actorId; }
export async function updateEvent(eventId: number, actorId: number, actorRole: string, input: { title: string; description?: string | null; category: string; location: string; startsAt: Date; endsAt?: Date | null; bannerUrl?: string | null }) { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const existing = (await db.select().from(events).where(eq(events.id, eventId)).limit(1))[0]; if (!existing || !canManageEvent(existing.organizerId, actorId, actorRole)) throw new Error('Event not found or not authorized'); await db.update(events).set(input).where(eq(events.id, eventId)); return { ...existing, ...input }; }
export async function updateEventStatus(eventId: number, actorId: number, actorRole: string, status: 'draft' | 'published' | 'cancelled') { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const event = (await db.select().from(events).where(eq(events.id, eventId)).limit(1))[0]; if (!event || !canManageEvent(event.organizerId, actorId, actorRole)) throw new Error('Not authorized to update this event'); await db.update(events).set({ status }).where(eq(events.id, eventId)); return status; }
export async function getEventBySlug(slug: string) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(events).where(eq(events.slug, slug)).limit(1); return result[0]; }
export async function listEventTicketTypes(eventId: number) { const db = await getDb(); if (!db) return []; return db.select().from(ticketTypes).where(eq(ticketTypes.eventId, eventId)); }
export async function listAttendeeTickets(attendeeEmail: string, userId?: number) { const db = await getDb(); if (!db) return []; const ownership = userId ? or(eq(tickets.attendeeEmail, attendeeEmail), eq(orders.buyerId, userId)) : eq(tickets.attendeeEmail, attendeeEmail); return db.select({ ticket: tickets }).from(tickets).leftJoin(orders, eq(tickets.orderId, orders.id)).where(ownership).orderBy(desc(tickets.issuedAt)).then((rows) => rows.map((row) => row.ticket)); }
export async function createComplimentaryInvite(input: typeof complimentaryInvites.$inferInsert) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");
  const event = (await db.select().from(events).where(eq(events.id, input.eventId)).limit(1))[0];
  const ticketType = (await db.select().from(ticketTypes).where(and(eq(ticketTypes.id, input.ticketTypeId), eq(ticketTypes.eventId, input.eventId))).limit(1))[0];
  if (!event || !ticketType) throw new Error("Event or ticket type not found");
  if (ticketType.quantitySold >= ticketType.quantityLimit) throw new Error("This ticket type is sold out");
  const inviteResult = await db.insert(complimentaryInvites).values(input);
  const orderResult = await db.insert(orders).values({ eventId: input.eventId, ticketTypeId: input.ticketTypeId, quantity: 1, buyerId: null, buyerName: input.recipientName?.trim() || input.recipientEmail, buyerEmail: input.recipientEmail, buyerPhone: input.recipientPhone, totalMinor: 0, status: "complimentary" });
  const orderId = Number(orderResult[0].insertId);
  const issued = { orderId, eventId: input.eventId, ticketTypeId: input.ticketTypeId, attendeeName: input.recipientName?.trim() || input.recipientEmail, attendeeEmail: input.recipientEmail, qrToken: createTicketToken(), status: "valid" as const };
  await db.insert(tickets).values(issued);
  await db.update(ticketTypes).set({ quantitySold: ticketType.quantitySold + 1 }).where(eq(ticketTypes.id, input.ticketTypeId));
  const emailSent = await deliverIssuedTicketEmail({ orderId, attendeeName: issued.attendeeName, attendeeEmail: issued.attendeeEmail, eventTitle: event.title, eventStartsAt: event.startsAt, eventLocation: event.location, ticketType: ticketType.name, quantity: 1, qrTokens: [issued.qrToken] }, async (status) => { await db.update(orders).set({ emailStatus: status }).where(eq(orders.id, orderId)); });
  void deliverIssuedTicketSms({ orderId, attendeeName: issued.attendeeName, attendeePhone: input.recipientPhone, eventTitle: event.title, quantity: 1, qrToken: issued.qrToken }, async (status) => { await db.update(orders).set({ smsStatus: status }).where(eq(orders.id, orderId)); });
  await db.update(complimentaryInvites).set({ status: "sent", sentAt: new Date() }).where(eq(complimentaryInvites.id, Number(inviteResult[0].insertId)));
  return { inviteId: Number(inviteResult[0].insertId), orderId, emailSent, smsStatus: "sending" as const };
}
export async function createOrder(input: typeof orders.$inferInsert) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");
  const result = await db.insert(orders).values(input);
  const orderId = Number(result[0].insertId);
  if (getTicketEmailTrigger({ totalMinor: input.totalMinor ?? 0, orderStatus: input.status ?? "pending" }) === "free_registration") {
    const issued = Array.from({ length: input.quantity }, () => ({ orderId, eventId: input.eventId, ticketTypeId: input.ticketTypeId, attendeeName: input.buyerName, attendeeEmail: input.buyerEmail, qrToken: createTicketToken(), status: "valid" as const }));
    await db.insert(tickets).values(issued);
    const ticket = (await db.select().from(ticketTypes).where(eq(ticketTypes.id, input.ticketTypeId)).limit(1))[0];
    const event = (await db.select().from(events).where(eq(events.id, input.eventId)).limit(1))[0];
    if (ticket && event) {
      await db.update(ticketTypes).set({ quantitySold: ticket.quantitySold + input.quantity }).where(eq(ticketTypes.id, input.ticketTypeId));
      await deliverIssuedTicketEmail({ orderId, attendeeName: input.buyerName, attendeeEmail: input.buyerEmail, eventTitle: event.title, eventStartsAt: event.startsAt, eventLocation: event.location, ticketType: ticket.name, quantity: input.quantity, qrTokens: issued.map((item) => item.qrToken) }, async (status) => { await db.update(orders).set({ emailStatus: status }).where(eq(orders.id, orderId)); });
      void deliverIssuedTicketSms({ orderId, attendeeName: input.buyerName, attendeePhone: input.buyerPhone, eventTitle: event.title, quantity: input.quantity, qrToken: issued[0].qrToken }, async (status) => { await db.update(orders).set({ smsStatus: status }).where(eq(orders.id, orderId)); });
    }
  }
  return orderId;
}
export async function getOrder(orderId: number) { const db = await getDb(); if (!db) return undefined; const result = await db.select().from(orders).where(eq(orders.id, orderId)).limit(1); return result[0]; }
export async function getTicketByToken(qrToken: string) { const db = await getDb(); if (!db) return undefined; const ticket = (await db.select().from(tickets).where(eq(tickets.qrToken, qrToken)).limit(1))[0]; if (!ticket) return undefined; const event = (await db.select().from(events).where(eq(events.id, ticket.eventId)).limit(1))[0]; const ticketType = (await db.select().from(ticketTypes).where(eq(ticketTypes.id, ticket.ticketTypeId)).limit(1))[0]; if (!event || !ticketType) return undefined; return { ticket, event, ticketType }; }
export async function resendTicketEmail(orderId: number, userId: number, userEmail?: string, sender: typeof sendTicketEmail = sendTicketEmail) { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const ownerFilter = userEmail ? or(eq(orders.buyerId, userId), eq(orders.buyerEmail, userEmail)) : eq(orders.buyerId, userId); const order = (await db.select().from(orders).where(and(eq(orders.id, orderId), ownerFilter)).limit(1))[0]; if (!order) throw new Error('Order not found or not authorized'); if (order.status !== 'paid') throw new Error('Tickets are not ready for this order'); const event = (await db.select().from(events).where(eq(events.id, order.eventId)).limit(1))[0]; const ticket = (await db.select().from(ticketTypes).where(eq(ticketTypes.id, order.ticketTypeId)).limit(1))[0]; const issued = await db.select().from(tickets).where(eq(tickets.orderId, order.id)).orderBy(desc(tickets.issuedAt)); if (!event || !ticket || !issued.length) throw new Error('No issued tickets found for this order'); const sent = await deliverIssuedTicketEmail({ orderId: order.id, attendeeName: order.buyerName, attendeeEmail: order.buyerEmail, eventTitle: event.title, eventStartsAt: event.startsAt, eventLocation: event.location, ticketType: ticket.name, quantity: issued.length, qrTokens: issued.map((item) => item.qrToken) }, async (status) => { await db.update(orders).set({ emailStatus: status }).where(eq(orders.id, order.id)); }, sender); return { sent, emailStatus: sent ? 'sent' as const : 'failed' as const }; }
export async function resendTicketSms(orderId: number, userId: number, userEmail?: string, sender: typeof sendTicketSms = sendTicketSms) { const db = await getDb(); if (!db) throw new Error("Database unavailable"); const ownerFilter = userEmail ? or(eq(orders.buyerId, userId), eq(orders.buyerEmail, userEmail)) : eq(orders.buyerId, userId); const order = (await db.select().from(orders).where(and(eq(orders.id, orderId), ownerFilter)).limit(1))[0]; if (!order) throw new Error("Order not found or not authorized"); if (order.status !== "paid") throw new Error("Tickets are not ready for this order"); const event = (await db.select().from(events).where(eq(events.id, order.eventId)).limit(1))[0]; if (!event || !order.buyerPhone) throw new Error("No ticket phone number found for this order"); const issued = await db.select().from(tickets).where(eq(tickets.orderId, order.id)).orderBy(desc(tickets.issuedAt)); if (!issued.length) throw new Error("No issued tickets found for this order"); const sent = await deliverIssuedTicketSms({ orderId: order.id, attendeeName: order.buyerName, attendeePhone: order.buyerPhone, eventTitle: event.title, quantity: order.quantity, qrToken: issued[0].qrToken }, async (status) => { await db.update(orders).set({ smsStatus: status }).where(eq(orders.id, order.id)); }, sender); return { sent, smsStatus: sent ? "sent" as const : "failed" as const }; }
export async function attachPaystackReference(orderId: number, reference: string) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");
  const order = (await db.select().from(orders).where(eq(orders.id, orderId)).limit(1))[0];
  if (!order) throw new Error("Order not found");
  if (order.status !== "pending" || order.totalMinor <= 0) throw new Error("This order is not eligible for Paystack checkout");
  if (order.paystackReference && order.paystackReference !== reference) throw new Error("This order already has a Paystack reference");
  if (!order.paystackReference) await db.update(orders).set({ paystackReference: reference }).where(eq(orders.id, orderId));
  return { ...order, paystackReference: order.paystackReference ?? reference };
}

export function shouldSkipPaystackFulfillment(order: { status: string; totalMinor: number }, existingTicketCount: number) {
  return order.status === "paid" && existingTicketCount >= 1;
}

export async function fulfillPaystackOrder(orderId: number, reference: string) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");
  const order = (await db.select().from(orders).where(eq(orders.id, orderId)).limit(1))[0];
  if (!order) throw new Error("Order not found");
  if (order.totalMinor <= 0) throw new Error("Free orders do not require Paystack fulfillment");
  if (order.paystackReference && order.paystackReference !== reference) throw new Error("Paystack reference does not match this order");
  const existingTickets = await db.select().from(tickets).where(eq(tickets.orderId, order.id));
  if (shouldSkipPaystackFulfillment(order, existingTickets.length) && existingTickets.length >= order.quantity) return { status: "paid" as const, alreadyFulfilled: true, ticketCount: existingTickets.length };
  await db.update(orders).set({ status: "paid", paystackReference: reference }).where(eq(orders.id, order.id));
  const issued = existingTickets.length ? existingTickets : Array.from({ length: order.quantity }, () => ({ orderId: order.id, eventId: order.eventId, ticketTypeId: order.ticketTypeId, attendeeName: order.buyerName, attendeeEmail: order.buyerEmail, qrToken: createTicketToken(), status: "valid" as const }));
  if (!existingTickets.length) await db.insert(tickets).values(issued);
  const ticket = (await db.select().from(ticketTypes).where(eq(ticketTypes.id, order.ticketTypeId)).limit(1))[0];
  const event = (await db.select().from(events).where(eq(events.id, order.eventId)).limit(1))[0];
  if (ticket && event && !existingTickets.length) {
    await db.update(ticketTypes).set({ quantitySold: ticket.quantitySold + order.quantity }).where(eq(ticketTypes.id, order.ticketTypeId));
    await deliverIssuedTicketEmail({ orderId: order.id, attendeeName: order.buyerName, attendeeEmail: order.buyerEmail, eventTitle: event.title, eventStartsAt: event.startsAt, eventLocation: event.location, ticketType: ticket.name, quantity: order.quantity, qrTokens: issued.map((item) => item.qrToken) }, async (status) => { await db.update(orders).set({ emailStatus: status }).where(eq(orders.id, order.id)); });
    void deliverIssuedTicketSms({ orderId: order.id, attendeeName: order.buyerName, attendeePhone: order.buyerPhone, eventTitle: event.title, quantity: order.quantity, qrToken: issued[0].qrToken }, async (status) => { await db.update(orders).set({ smsStatus: status }).where(eq(orders.id, order.id)); });
  }
  return { status: "paid" as const, alreadyFulfilled: false, ticketCount: issued.length };
}

export async function submitMtnPayment(input: typeof paymentSubmissions.$inferInsert) { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const result = await db.insert(paymentSubmissions).values(input); return Number(result[0].insertId); }
export async function listPendingPayments(organizerId: number) { const db = await getDb(); if (!db) return []; const organizerEvents = await listOrganizerEvents(organizerId); const ids = organizerEvents.map((event) => event.id); if (!ids.length) return []; const organizerOrders = await db.select().from(orders).where(or(...ids.map((id) => eq(orders.eventId, id)))); const orderIds = organizerOrders.map((order) => order.id); if (!orderIds.length) return []; return db.select().from(paymentSubmissions).where(or(...orderIds.map((id) => eq(paymentSubmissions.orderId, id)))).orderBy(desc(paymentSubmissions.createdAt)); }
export function canReviewPayment(eventOwnerId: number, reviewerId: number, reviewerRole: 'user' | 'admin') { return reviewerRole === 'admin' || eventOwnerId === reviewerId; }
export function shouldRevealPaidTickets(orderStatus: string, paymentStatus: string) { return orderStatus === 'paid' && paymentStatus === 'approved'; }
export function canCheckInTicket(eventOwnerId: number, reviewerId: number, reviewerRole: 'user' | 'admin') { return reviewerRole === 'admin' || eventOwnerId === reviewerId; }
export async function checkInTicket(qrToken: string, reviewerId: number, reviewerRole: 'user' | 'admin') { const db = await getDb(); if (!db) throw new Error('Database unavailable'); const ticket = (await db.select().from(tickets).where(eq(tickets.qrToken, qrToken)).limit(1))[0]; if (!ticket) throw new Error('Ticket not found'); const event = (await db.select().from(events).where(eq(events.id, ticket.eventId)).limit(1))[0]; if (!event || !canCheckInTicket(event.organizerId, reviewerId, reviewerRole)) throw new Error('Not authorized to check in this ticket'); if (ticket.status === 'checked_in') return { status: 'already_checked_in' as const, ticket }; if (ticket.status !== 'valid') throw new Error('Ticket is not valid for check-in'); await db.update(tickets).set({ status: 'checked_in', checkedInAt: new Date() }).where(eq(tickets.id, ticket.id)); return { status: 'checked_in' as const, ticket: { ...ticket, status: 'checked_in' as const, checkedInAt: new Date() } }; }
export async function approveMtnPayment(paymentId: number, reviewerId: number, reviewerRole: "user" | "admin", decision: "approved" | "rejected", reviewNote?: string) {
  const db = await getDb();
  if (!db) throw new Error("Database unavailable");
  const payment = (await db.select().from(paymentSubmissions).where(eq(paymentSubmissions.id, paymentId)).limit(1))[0];
  if (!payment) throw new Error("Payment submission not found");
  const order = (await db.select().from(orders).where(eq(orders.id, payment.orderId)).limit(1))[0];
  if (!order) throw new Error("Order not found");
  const event = (await db.select().from(events).where(eq(events.id, order.eventId)).limit(1))[0];
  if (!event || !canReviewPayment(event.organizerId, reviewerId, reviewerRole)) throw new Error("Not authorized to review this payment");
  if (payment.status !== "pending") return payment.status;
  await db.update(paymentSubmissions).set({ status: decision, reviewedBy: reviewerId, reviewedAt: new Date(), reviewNote: reviewNote ?? null }).where(eq(paymentSubmissions.id, paymentId));
  if (decision === "rejected") {
    await db.update(orders).set({ status: "cancelled" }).where(eq(orders.id, order.id));
    return decision;
  }
  await db.update(orders).set({ status: "paid" }).where(eq(orders.id, order.id));
  const issued = Array.from({ length: order.quantity }, () => ({ orderId: order.id, eventId: order.eventId, ticketTypeId: order.ticketTypeId, attendeeName: order.buyerName, attendeeEmail: order.buyerEmail, qrToken: createTicketToken(), status: "valid" as const }));
  await db.insert(tickets).values(issued);
  const ticket = (await db.select().from(ticketTypes).where(eq(ticketTypes.id, order.ticketTypeId)).limit(1))[0];
  if (ticket) {
    await db.update(ticketTypes).set({ quantitySold: ticket.quantitySold + order.quantity }).where(eq(ticketTypes.id, order.ticketTypeId));
    if (getTicketEmailTrigger({ totalMinor: order.totalMinor, orderStatus: "paid" }) === "approved_payment") {
      await deliverIssuedTicketEmail({ orderId: order.id, attendeeName: order.buyerName, attendeeEmail: order.buyerEmail, eventTitle: event.title, eventStartsAt: event.startsAt, eventLocation: event.location, ticketType: ticket.name, quantity: order.quantity, qrTokens: issued.map((item) => item.qrToken) }, async (status) => { await db.update(orders).set({ emailStatus: status }).where(eq(orders.id, order.id)); });
    }
    void deliverIssuedTicketSms({ orderId: order.id, attendeeName: order.buyerName, attendeePhone: order.buyerPhone, eventTitle: event.title, quantity: order.quantity, qrToken: issued[0].qrToken }, async (status) => { await db.update(orders).set({ smsStatus: status }).where(eq(orders.id, order.id)); });
  }
  return decision;
}
export async function getOrganizerSummary(organizerId: number) {
  const db = await getDb();
  if (!db) return { events: [], orders: [], guests: [] };
  const organizerEvents = await listOrganizerEvents(organizerId);
  const eventIds = organizerEvents.map((event) => event.id);
  const organizerOrders = eventIds.length ? await db.select().from(orders).where(or(...eventIds.map((id) => eq(orders.eventId, id)))) : [];
  const orderIds = organizerOrders.map((order) => order.id);
  const payments = orderIds.length ? await db.select().from(paymentSubmissions).where(or(...orderIds.map((id) => eq(paymentSubmissions.orderId, id)))) : [];
  const issuedTickets = orderIds.length ? await db.select().from(tickets).where(or(...orderIds.map((id) => eq(tickets.orderId, id)))) : [];
  const types = eventIds.length ? await db.select().from(ticketTypes).where(or(...eventIds.map((id) => eq(ticketTypes.eventId, id)))) : [];
  const paymentByOrder = new Map(payments.map((payment) => [payment.orderId, payment]));
  const ticketsByOrder = new Map<number, typeof issuedTickets>();
  for (const ticket of issuedTickets) ticketsByOrder.set(ticket.orderId, [...(ticketsByOrder.get(ticket.orderId) ?? []), ticket]);
  const typeById = new Map(types.map((type) => [type.id, type]));
  const eventById = new Map(organizerEvents.map((event) => [event.id, event]));
  const guests = organizerOrders.map((order) => {
    const event = eventById.get(order.eventId);
    const payment = paymentByOrder.get(order.id);
    const orderTickets = ticketsByOrder.get(order.id) ?? [];
    return { orderId: order.id, eventId: order.eventId, eventTitle: event?.title ?? "Event", ticketTypeName: typeById.get(order.ticketTypeId)?.name ?? "Ticket", ticketCount: orderTickets.length, ticketDetails: orderTickets.map((ticket) => ({ id: ticket.id, qrToken: ticket.qrToken, status: ticket.status, issuedAt: ticket.issuedAt, checkedInAt: ticket.checkedInAt })), name: order.buyerName, email: order.buyerEmail, phone: order.buyerPhone, quantity: order.quantity, totalMinor: order.totalMinor, accessType: order.status === "complimentary" ? "complimentary" as const : order.totalMinor === 0 ? "free" as const : "paid" as const, transactionId: order.paystackReference ?? payment?.transactionId ?? null, paymentStatus: order.status === "paid" ? "paid" as const : order.status === "cancelled" ? "cancelled" as const : order.totalMinor === 0 ? "free" as const : payment?.status ?? null, emailStatus: order.emailStatus, smsStatus: order.smsStatus, createdAt: order.createdAt };
  });
  return { events: organizerEvents, orders: organizerOrders, guests };
}
