import { BrowserContext, Page, Request } from '@playwright/test'

const pendingRequests = new WeakMap<Page, Set<Request>>()

function isApiRequest(request: Request, apiOrigin: string, apiPort: string): boolean {
  try {
    const url = new URL(request.url())
    return url.origin === apiOrigin || url.port === apiPort || url.pathname.includes('/trpc')
  } catch {
    return false
  }
}

function trackPage(page: Page, apiOrigin: string, apiPort: string) {
  if (pendingRequests.has(page)) {
    return
  }

  const trackedRequests = new Set<Request>()
  pendingRequests.set(page, trackedRequests)

  const markCompleted = (request: Request) => {
    if (trackedRequests.has(request)) {
      trackedRequests.delete(request)
    }
  }

  page.on('request', (request) => {
    if (isApiRequest(request, apiOrigin, apiPort)) {
      trackedRequests.add(request)
    }
  })
  page.on('requestfinished', markCompleted)
  page.on('requestfailed', markCompleted)
  page.on('close', () => {
    pendingRequests.delete(page)
  })
}

/**
 * Registers a browser context to track API request stability across all pages.
 *
 * This function sets up request tracking for all current and future pages in the given
 * browser context. It monitors API requests to determine when the application becomes
 * "idle" (no pending API requests). The tracking includes requests to the specified
 * API origin, port, or any tRPC endpoints.
 *
 */
export function registerContextForStability(context: BrowserContext, apiUrl: URL) {
  const apiOrigin = apiUrl.origin
  const apiPort = apiUrl.port || '80'

  context.on('page', (page) => {
    trackPage(page, apiOrigin, apiPort)
  })

  for (const page of context.pages()) {
    trackPage(page, apiOrigin, apiPort)
  }
}

/**
 * Waits for a page to become "idle" by ensuring all tracked API requests have completed.
 *
 * This function polls the page's pending request tracker and waits until no API requests
 * are in-flight. It's useful for ensuring the application has finished loading data
 * before proceeding with test assertions or interactions.
 *
 */
export async function waitForAppIdle(page: Page, timeout = 10_000) {
  const start = Date.now()
  while (Date.now() - start < timeout) {
    if (page.isClosed()) {
      return
    }

    const tracked = pendingRequests.get(page)
    if (!tracked || tracked.size === 0) {
      return
    }

    await page.waitForTimeout(50)
  }

  throw new Error(`Timed out waiting for app to become idle after ${timeout}ms`)
}
