import { Page, TestFixture, TestInfo } from "@playwright/test";

//#region src/index.d.ts
type SessionValueType = string | number | Date | null | undefined | Array<SessionValueType> | {
  [key: string]: SessionValueType;
};
type SessionType = Record<string, SessionValueType>;
interface PersonaOptions<Session extends SessionType> {
  /**
   * Create a new session for this persona.
   * Returns the context object to share state across other methods.
   *
   * @example
   * async createSession({ page }) {
   *   const user = await createMockUser()
   *   await page.goto('/login')
   *   await page.getByLabel('Username').fill(user.name)
   *   await page.getByLagbel('Password').fill(user.password)
   *   await page.getByRole('button', { name: 'Log in' }).click()
   *
   *   return { user }
   * }
   */
  createSession: CreateSessionFunction<Session>;
  /**
   * Verify that the given session belongs to this persona.
   * Used to determine if the authentication state stored on disk
   * should still be used or a new session should be created.
   *
   * @example
   * async verifySession({ page, session }) {
   *   await page.goto('/dashboard')
   *   await expect(page.getByText(session.user.name)).toBeVisible()
   * }
   */
  verifySession: VerifySessionFunction<Session>;
  /**
   * Destroy the given session.
   * Used to properly dispose of test resources related to a stored session
   * (e.g. test records in a database).
   *
   * @example
   * async destroySession({ session }) {
   *   await deleteUser({ where: { id: session.user.id } })
   * }
   */
  destroySession?: DestroySessionFunction<Session>;
}
interface CreateSessionContext {
  page: Page;
}
interface VerifySessionContext<Session extends SessionType> {
  session: Session;
  page: Page;
}
interface DestroySessionContext<Session extends SessionType> {
  session: Session;
  page: Page;
}
interface Persona<Name extends string, Session extends SessionType> {
  name: Name;
  createSession: CreateSessionFunction<Session>;
  verifySession: VerifySessionFunction<Session>;
  destroySession?: DestroySessionFunction<Session>;
}
type CreateSessionFunction<Session extends SessionType> = (context: CreateSessionContext, testInfo: TestInfo) => Promise<Session>;
type VerifySessionFunction<Session extends SessionType> = (context: VerifySessionContext<Session>, testInfo: TestInfo) => Promise<void>;
type DestroySessionFunction<Session extends SessionType> = (context: DestroySessionContext<Session>, testInfo: TestInfo) => Promise<void>;
declare function definePersona<Name extends string, Session extends SessionType>(name: Name, options: PersonaOptions<Session>): Persona<Name, Session>;
type AuthenticateFunction<Personas extends Array<Persona<any, any>>> = <Name extends ExtractPersonaNames<Personas>>(options: {
  as: Name;
}) => Promise<MapPersonasToRecord<Personas>[Name]>;
type MapPersonasToRecord<Personas extends Array<Persona<any, any>>> = Personas extends Array<infer P> ? { [Name in P extends Persona<infer Name, any> ? Name : never]: P extends Persona<Name, infer Session> ? Session : never } : never;
type ExtractPersonaNames<Personas extends Array<Persona<any, any>>> = Personas extends Array<infer P> ? P extends Persona<infer Name, any> ? Name : never : never;
declare function combinePersonas<Personas extends Array<Persona<any, any>>>(...personas: Personas): TestFixture<AuthenticateFunction<Personas>, any>;
//#endregion
export { AuthenticateFunction, CreateSessionContext, CreateSessionFunction, DestroySessionContext, DestroySessionFunction, Persona, PersonaOptions, SessionType, VerifySessionContext, VerifySessionFunction, combinePersonas, definePersona };