/** Mirrors the payloads returned by core's /v1/admin-console/event-analytics routes. */

export type RevenueByCurrency = {
  currency: string;
  revenue: number;
  events: number;
};

export type EventSummary = {
  totalEvents: number;
  uniqueMembers: number;
  uniqueEventTypes: number;
  revenueByCurrency: RevenueByCurrency[];
  /**
   * Loyalty points, and how many events carried any. Most events in this
   * dataset have no amount/currency, so points are usually the only quantity.
   */
  totalPoints: number;
  pointsEvents: number;
  topEventType: { eventType: string; events: number } | null;
  firstEventAt: string | null;
  lastEventAt: string | null;
  /** The scan hit core's aggregation ceiling — the figures are a partial view. */
  truncated: boolean;
  scannedDocs: number;
};

export type DayActivity = {
  day: string;
  events: number;
  points: number;
  members: number;
};

export type PropertyActivity = {
  property: string;
  events: number;
  points: number;
  members: number;
};

export type SourceActivity = {
  source: string;
  events: number;
  points: number;
  members: number;
};

export type SummaryResponse = {
  summary: EventSummary;
  /** Collection names: add_to_cart, booking_hold_initiated, purchase. */
  availableEventTypes: string[];
  availableCurrencies: string[];
  availableProperties: string[];
  availableSources: string[];
  /** Activity per day across every member, for the module timeline. */
  byDay: DayActivity[];
  /** Per-property roll-up across every member. */
  byProperty: PropertyActivity[];
  /** Per-source roll-up. `sourceOfEvent` cuts across event types. */
  bySource: SourceActivity[];
};

/** Member details from the Updot member DB, joined on the event's member number. */
export type EventMemberProfile = {
  memberNumber: string;
  memberId: string | null;
  membershipNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  country: string | null;
  nationality: string | null;
  dateOfBirth: string | null;
  accountType: string | null;
  accountStatus: string | null;
  signupPromoCode: string | null;
  memberSince: string | null;
};

export type MemberAnalyticsRow = {
  memberId: string;
  memberName: string | null;
  memberEmail: string | null;
  /** Null when the member number has no match in the member DB. */
  profile?: EventMemberProfile | null;
  events: number;
  eventTypes: number;
  revenueByCurrency: RevenueByCurrency[];
  points: number;
  properties: string[];
  /**
   * Events per type for this member, keyed by collection name. Reads directly
   * as a funnel: add_to_cart 4 → booking_hold_initiated 2 → purchase 1.
   */
  eventCounts: Record<string, number>;
  firstSeen: string | null;
  lastSeen: string | null;
};

export type MemberAnalyticsResponse = {
  rows: MemberAnalyticsRow[];
  total: number;
  page: number;
  pageSize: number;
  totalPages: number;
  truncated: boolean;
};

export type EventTypeAnalyticsRow = {
  eventType: string;
  events: number;
  uniqueMembers: number;
  revenueByCurrency: RevenueByCurrency[];
  points: number;
  sharePct: number;
};

export type EventTypeAnalyticsResponse = {
  rows: EventTypeAnalyticsRow[];
  total: number;
  truncated: boolean;
};

export type EventRecord = {
  id: string;
  /** The collection the document came from — this is the event type. */
  eventType: string;
  /** Where in the app the event originated, e.g. "properties". Not the type. */
  source: string | null;
  occurredAt: string | null;
  /** `createdAt` exactly as the producer wrote it. What reports display. */
  createdAtRaw: string | null;
  memberId: string | null;
  memberName: string | null;
  memberEmail: string | null;
  bookingRef: string | null;
  revenue: number | null;
  currency: string | null;
  points: number | null;
  propertyName: string | null;
  experienceName: string | null;
  memberOffer: string | null;
  raw: Record<string, unknown>;
};

export type EventRecordsResponse = {
  records: EventRecord[];
  nextCursor: string | null;
  hasMore: boolean;
  /** Null while a text search is active — core cannot count that predicate. */
  total: number | null;
};

export type MemberDayPoint = {
  day: string;
  events: number;
  points: number;
  /** Events per type on that day, keyed by collection name. */
  byType: Record<string, number>;
};

export type MemberDetailResponse = {
  memberId: string;
  profile: EventMemberProfile | null;
  /** Null when the member produced no events under the active filter. */
  member: MemberAnalyticsRow | null;
  summary: EventSummary;
  eventTypes: EventTypeAnalyticsRow[];
  /** Every event for this member, newest first, up to core's per-member cap. */
  records: EventRecord[];
  /** True when the member has more events than the page read. */
  recordsTruncated: boolean;
  byDay: MemberDayPoint[];
  byProperty: { property: string; events: number; points: number }[];
  bySource: { source: string; events: number; points: number }[];
};

/** Display name for a member: real name if known, otherwise the number. */
export function memberDisplayName(row: {
  memberId: string;
  memberName?: string | null;
  profile?: EventMemberProfile | null;
}): string {
  return row.profile?.fullName ?? row.memberName ?? row.memberId;
}

/** The single filter object every panel on the page reads from. */
export type EventAnalyticsFilters = {
  from: string | null;
  to: string | null;
  eventTypes: string[];
  memberId: string | null;
  currency: string | null;
  property: string | null;
  source: string | null;
  search: string | null;
};

export const EMPTY_FILTERS: EventAnalyticsFilters = {
  from: null,
  to: null,
  eventTypes: [],
  memberId: null,
  currency: null,
  property: null,
  source: null,
  search: null,
};

/**
 * Collection names are snake_case identifiers from the producer. Titled for
 * display only — the raw value stays the filter value and the export column, so
 * nothing downstream has to reverse this.
 */
export function eventTypeLabel(eventType: string): string {
  return eventType
    .split(/[_\-\s]+/)
    .filter(Boolean)
    .map((word, i) =>
      i === 0 ? word.charAt(0).toUpperCase() + word.slice(1) : word,
    )
    .join(" ");
}
