import { RegistrationOnlineSessionActivationStatus } from 'src/common/enum/registration-online-session-activation-status.enum';
import {
  ProgramEligibleKpiCategory,
  ProgramEligibleKpiFilter,
} from 'src/common/enum/program-eligible-kpi.enum';

/** Per-registration result of a bulk panelist registration. */
export type PanelistBulkOutcome =
  | { status: 'registered' | 'skipped' }
  | { status: 'failed'; error: unknown };

/** Running counts shared across a bulk registration job's paths. */
export interface BulkTally {
  registered: number;
  skipped: number;
  failed: number;
}

/** One row of the admin registration ↔ join-URL view. */
export interface RegistrationWithJoinUrl {
  registrationId: number;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  registrationStatus: string;
  seatAllocated: boolean;
  joinUrl: string | null;
  isPanelist: boolean | null;
  /** Null when the registrant has no provisioned extension for the scoped session yet. */
  activationStatus: RegistrationOnlineSessionActivationStatus | null;
}

/** Live active/inactive breakdown of a session's eligible (provisioned) registrants. */
export interface EligibleActivationSummary {
  onlineSessionId: number;
  totalEligible: number;
  activeCount: number;
  inactiveCount: number;
}

/** One RM, for the `rmContact` filter's option list — filter by id (a real FK), not name. */
export interface RmContactOption {
  id: number;
  name: string;
}

/** Lean per-registration status row for a program, for the Overall Analytics KPI aggregate. */
export interface ProgramRegistrationStatusRow {
  registrationId: number;
  registrationStatus: string;
  activationStatus: RegistrationOnlineSessionActivationStatus;
  /** "Total Attendees" is every registration with an allocated seat — see ZoomProgramAnalyticsKpis.totalAttendees. */
  seatAllocated: boolean;
  fullName: string;
}

/**
 * Outcome of the registration-level activation toggle: which upcoming sessions'
 * extension rows were actually flipped (already-in-state and currently-live
 * sessions are skipped; past sessions are never in scope).
 */
export interface RegistrationActivationResult {
  registrationId: number;
  activationStatus: RegistrationOnlineSessionActivationStatus;
  updatedOnlineSessionIds: number[];
}

/**
 * One row of a program's seat-allocated registrant list — no single session
 * context, so `joinUrl` is the registrant's LATEST extension row across ANY
 * session (see RegistrationWithJoinUrl for the per-session-scoped view).
 * `activationStatus` here is the registration-level rollup
 * (ProgramRegistration.activationStatus), not a per-session value.
 */
export interface ProgramEligibleRegistrationRow {
  registrationId: number;
  registrationSeqNumber: string | null;
  fullName: string | null;
  email: string | null;
  mobile: string | null;
  seatAllocated: 'Yes' | 'No';
  /** Capitalized for display (e.g. "Male"), not the raw column value. */
  gender: string | null;
  profileUrl: string | null;
  /** Capitalized for display (e.g. "Active"/"Inactive") — use the `filters.activationStatus` query value (lowercase) to filter, not this. */
  activationStatus: string;
  /** RM's display name, or the free-text "other" contact when the RM is recorded as "Other". Capitalized for display. */
  rmContact: string | null;
  /** Formatted label from the registrant's most recent payment record (by paymentDate/updatedAt). */
  paymentStatus: string | null;
  registrationMode: string | null;
  /** Computed from dob as of today; null when dob is unknown. */
  age: number | null;
  /** City, falling back to the free-text "other" city when recorded as "Other" — same convention as the main list view's location. */
  city: string | null;
  /** The registrant's latest join link across any session; null if never provisioned. */
  joinUrl: string | null;
  /** Attended vs completed program sessions, e.g. "3/5" (attended = is_attended; completed = ended sessions). */
  attendedSessions: string;
  /**
   * Per-session attendance breakdown for ALL of the program's scheduled sessions
   * (including ones not yet started), ordered chronologically by start time. Each
   * entry carries the session's lifecycle state and whether the registrant
   * attended — richer than the "3/5" summary in {@link attendedSessions} (whose
   * denominator only counts sessions that have already started).
   */
  sessionAttendance: ProgramSessionAttendance[];
}

/** One program session's identity + timing, ordered chronologically — the columns backing the attendance breakdown. */
export interface ConductedSession {
  id: number;
  name: string | null;
  startsAt: Date | null;
  endsAt: Date | null;
  /** The session's provisioned online session, if any — null when never set up online. */
  onlineSessionId: number | null;
}

/** Lifecycle state of a session relative to now. */
export type SessionAttendanceState = 'not_started' | 'in_progress' | 'completed';

/**
 * One session's attendance status for a single registrant, used in
 * {@link ProgramEligibleRegistrationRow.sessionAttendance}.
 */
export interface ProgramSessionAttendance {
  sessionId: number;
  /** The session's name (program_session.name). */
  name: string | null;
  /**
   * Lifecycle state of the session relative to now: 'not_started' (starts in the
   * future), 'in_progress' (started, not yet ended), or 'completed' (ended).
   */
  sessionState: SessionAttendanceState;
  /** true when program_user_attendance.is_attended = true for this session, else false (absent). */
  status: boolean;
}

/**
 * Query for {@link ProgramEligibleRegistrationRow}'s list — `filters` keys match
 * the `filters` array returned alongside the data (gender, registrationMode,
 * age, registrationStatus, activationStatus, paymentMode, paymentStatus,
 * rmContact, userType). Most are equality matches against that field's coarse
 * filter code (see PROGRAM_ELIGIBLE_REGISTRATIONS_STATIC_FILTERS); `rmContact` is the
 * RM's user id (a string, since query params are always strings), not their name.
 * `userType` is the registrant's own account type (Org/Seeker) and may be an array —
 * multiple selections are OR'd, see `resolveUserTypeFilter`. `Seeker` also covers
 * "registered for someone else" rows, which have no user account of their own.
 */
export interface ProgramEligibleRegistrationsQuery {
  offset: number;
  limit: number;
  search?: string;
  sortKey?: string;
  sortOrder?: 'ASC' | 'DESC';
  filters?: Record<string, string | string[]>;
  /**
   * Server-derived scope, never client-supplied: set to the caller's own user id
   * when they hold the RM role, restricting results to their own contacts (same
   * convention as the main registration list view). Overrides any client
   * `filters.rmContact`.
   */
  rmContactId?: number;
  /** v1 only — clicking a {@link ProgramEligibleKpiTile} re-issues the query with its own category/filter, narrowing the table to the rows behind that count. Must be passed together with `kpiFilter`. */
  kpiCategory?: ProgramEligibleKpiCategory;
  /** v1 only — see `kpiCategory`. `ALL`/omitted = no narrowing. Resolved to the matching registrant ids by `ZoomRegistrationRepository.resolveProgramEligibleKpiFilterIds`, the exact same computation {@link ProgramEligibleKpis} counts from, so a tile's value and the rows behind it can never disagree. */
  kpiFilter?: ProgramEligibleKpiFilter;
}

/**
 * Program-wide KPI tiles for the eligible-registrations screen. Always computed
 * over the FULL eligible set for the program — ignores the list's `search`/
 * `filters` (only the RM visibility scope applies, same as the row list) — so
 * the tiles stay stable while the table below them is filtered/searched.
 */
export interface ProgramEligibleKpis {
  /** Every seat-allocated, non-deleted registrant of the program (in RM scope, if any) — the same set the eligible-registrations list draws its unfiltered rows from. */
  totalEligible: number;
  /** Registrants whose current `activationStatus` (registration-level rollup) is ACTIVE. */
  active: number;
  /** Registrants whose current `activationStatus` is INACTIVE. `active + inactive === totalEligible`. */
  inactive: number;
  /**
   * Unique registrants who missed at least one of the program's already-conducted
   * sessions (no `program_user_attendance.is_attended = true` row for that
   * session). Registrants are only considered once the program has at least one
   * conducted session; with none conducted yet this is 0.
   */
  absentAtLeastOnce: number;
  /**
   * Unique registrants who attended EVERY one of the program's already-conducted
   * sessions (zero absences so far). Same eligibility rule as
   * {@link absentAtLeastOnce} — every eligible registrant falls into exactly one
   * of the two once at least one session has been conducted.
   */
  presentForAll: number;
  /** Total scheduled sessions for the program (conducted + upcoming), not just conducted ones. */
  totalSessions: number;
  /**
   * Unique registrants eligible for the program's FINAL (last, chronologically)
   * session: attended EVERY earlier session that has already started, AND held
   * `activationStatus = ACTIVE` on that earlier session's own extension row at
   * the time (frozen once a session elapses — see
   * `ZoomRegistrationService.setRegistrationActivation`, which never touches past
   * sessions). Mirrors `ZoomFinalSessionConfirmService.resolveFinalSession`'s
   * final/non-final split, plus the activation check this KPI additionally
   * requires. Null when the program has 1 or 0 sessions (no "final session"
   * concept applies — same guard as that service).
   */
  eligibleForFinalSession: number | null;
  /**
   * The strict complement of {@link eligibleForFinalSession} —
   * `totalEligible - eligibleForFinalSession` — so the "Eligible"/"Not Eligible"
   * tiles always add up to `totalEligible` (unlike {@link absentAtLeastOnce},
   * which uses a different session-set/criteria and is NOT this tile's
   * complement). Null exactly when `eligibleForFinalSession` is null.
   */
  notEligibleForFinalSession: number | null;
}

/**
 * One eligible-registrations KPI tile — the same clickable-tile shape the zoom module's v1 attendee
 * table uses (`SessionKpiTile` in zoom-analytics.interface.ts): `label`/`value`, plus `kpiCategory`/
 * `kpiFilter` when clicking the tile narrows the table to the rows behind that count. `totalEligible`/
 * `totalSessions` are plain stats (no single filter narrows to "everyone", nor is either keyed by a
 * `ProgramEligibleKpiFilter`) — those two tiles never carry `kpiCategory`/`kpiFilter`.
 */
export interface ProgramEligibleKpiTile {
  label: string;
  value: number;
  kpiCategory?: ProgramEligibleKpiCategory;
  kpiFilter?: ProgramEligibleKpiFilter;
}
