import test$2, { test as test$1, expect, devices, defineConfig } from '@playwright/test';
export { expect } from '@playwright/test';
import winston from 'winston';
import axios from 'axios';
import AxeBuilder from '@axe-core/playwright';

const logger = winston.createLogger({
    level: process.env.CI ? 'silent' : 'info',
    format: winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
        const { timestamp, level, message, ...meta } = info;
        const formattedMeta = Object.keys(meta).length
            ? `\n${JSON.stringify(meta, undefined, 2)}`
            : '';
        return `${timestamp} ${level}: ${message} ${formattedMeta}`;
    })),
    transports: [new winston.transports.Console()],
});

/**
 * Check if the current browser is Firefox
 */
function isFirefox(page) {
    var _a;
    return ((_a = page.context().browser()) === null || _a === void 0 ? void 0 : _a.browserType().name()) === 'firefox';
}
/**
 * Check if an error is a navigation aborted error (Firefox-specific)
 */
function isNavigationAbortedError(error) {
    var _a;
    const message = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
    return message.includes('NS_BINDING_ABORTED') || message.includes('Navigation interrupted');
}
/**
 * Check if an error is a Safari navigation policy error
 */
function isSafariNavigationPolicyError(error) {
    var _a;
    const message = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
    return message.includes('Navigation canceled by policy check');
}
/**
 * Check if the current URL matches a given pattern
 */
function urlMatchesPattern(currentUrl, pattern) {
    try {
        const url = new URL(currentUrl);
        if (typeof pattern === 'function')
            return pattern(url);
        if (pattern instanceof RegExp)
            return pattern.test(currentUrl);
        // Glob pattern - convert to regex
        const regexPattern = pattern
            .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars except * and ?
            .replace(/\*\*/g, '.*')
            .replace(/\*/g, '[^/]*');
        return new RegExp(`^${regexPattern}$`).test(currentUrl);
    }
    catch (_a) {
        return false;
    }
}
/**
 * Safe wrapper around page.waitForURL that handles browser-specific navigation errors
 *
 * Firefox has stricter navigation lifecycle handling that can cause NS_BINDING_ABORTED
 * errors when multiple rapid navigations occur (React router redirects, auth redirects).
 *
 * Safari can throw "Navigation canceled by policy check" errors during cross-origin
 * navigations or when security policies interrupt the navigation flow.
 *
 * This utility:
 * 1. Uses `waitUntil: 'commit'` for Firefox (faster, avoids abort)
 * 2. Includes retry logic for NS_BINDING_ABORTED errors (Firefox)
 * 3. Handles "Navigation canceled by policy check" errors (Safari)
 * 4. Checks if URL is already correct before failing
 */
async function safeWaitForURL(page, urlPattern, options = {}) {
    const { timeout = 60000, waitUntil = isFirefox(page) ? 'commit' : 'domcontentloaded', maxRetries = 3, } = options;
    let lastError = null;
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await page.waitForLoadState('load', { timeout });
            await page.waitForURL(urlPattern, { timeout, waitUntil });
            return;
        }
        catch (error) {
            lastError = error;
            // Handle Firefox NS_BINDING_ABORTED errors
            if (isNavigationAbortedError(error)) {
                // Check if we're already at the target URL
                if (urlMatchesPattern(page.url(), urlPattern)) {
                    return;
                }
                if (attempt < maxRetries) {
                    // Exponential backoff between retries
                    await page.waitForTimeout(500 * attempt);
                    continue;
                }
            }
            // Handle Safari navigation policy errors
            if (isSafariNavigationPolicyError(error)) {
                await page.waitForTimeout(3000);
                await page.waitForLoadState('domcontentloaded');
                // Check if we're at the target URL after waiting
                if (urlMatchesPattern(page.url(), urlPattern)) {
                    return;
                }
                if (attempt < maxRetries) {
                    continue;
                }
            }
            throw error;
        }
    }
    throw lastError;
}

async function waitForPageLoad(page) {
    logger.info(`Waiting for ${page.url()} load...`);
    try {
        logger.info('waiting for page load');
        await page.waitForLoadState('load', { timeout: 60000 });
        logger.info('waiting for dom content to be loaded');
        await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
        logger.info('waiting for network idle');
        await page.waitForLoadState('networkidle', { timeout: 60000 });
        await page
            .waitForResponse((response) => response.status() === 200, {
            timeout: 10000,
        })
            .catch(() => {
            logger.info('No more network requests detected');
        });
        logger.info('checking for dialog');
        const dialog = await Promise.race([
            page.waitForEvent('dialog', { timeout: 1000 }).catch(() => null),
            Promise.resolve(null),
        ]);
        if (dialog) {
            logger.info('dialog detected');
            logger.info(`Dismissing dialog with message: ${dialog.message()}`);
            await dialog.dismiss();
        }
    }
    catch (error) {
        logger.error('Error waiting for page load:', error);
        throw error;
    }
}
async function closeWithEsc(page) {
    await page.waitForTimeout(500);
    await page.keyboard.press('Escape');
    await page.waitForTimeout(500);
}
const test = test$1.extend({
    step: async ({ page }, use, testInfo) => {
        await use(async (title, fn) => {
            await fn();
        });
    },
});
const retry = async (action, errorMessage, retryCount = 5) => {
    let count = 0;
    while (count < retryCount) {
        try {
            await action();
            break;
        }
        catch (error) {
            count += 1;
            logger.error(`${errorMessage}: ${error}`);
        }
    }
};
const isJSON = (value) => {
    try {
        JSON.parse(value);
        return true;
    }
    catch (error) {
        return false;
    }
};

class MailsacClient {
    constructor(apiKey) {
        this.baseUrl = 'https://mailsac.com/api';
        this.apiKey = apiKey;
    }
    /**
     * Get all emails for a specific inbox
     * @param inbox - The inbox address (e.g., 'test@mailsac.com' or just 'test')
     */
    async getEmails(inbox) {
        var _a;
        const formattedInbox = inbox.includes('@') ? inbox : `${inbox}@mailsac.com`;
        try {
            const response = await axios.get(`${this.baseUrl}/addresses/${formattedInbox}/messages`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error fetching emails:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error fetching emails:', error);
            }
            throw error;
        }
    }
    /**
     * Get a specific email by ID
     * @param email - The email address
     * @param messageId - The ID of the email to fetch
     */
    async getEmailById(email, messageId) {
        var _a;
        const formattedEmail = email.includes('@') ? email : `${email}@mailsac.com`;
        try {
            const response = await axios.get(`${this.baseUrl}/raw/${formattedEmail}/${messageId}`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error fetching email content:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error fetching email content:', error);
            }
            throw error;
        }
    }
    /**
     * Delete a specific email by ID
     * @param emailId - The ID of the email to delete
     */
    async deleteEmail(emailId) {
        var _a;
        try {
            const response = await axios.delete(`${this.baseUrl}/messages/${emailId}`, {
                headers: {
                    'Mailsac-Key': this.apiKey,
                },
            });
            return response.data;
        }
        catch (error) {
            if (axios.isAxiosError(error)) {
                console.error('Error deleting email:', ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error.message);
            }
            else {
                console.error('Error deleting email:', error);
            }
            throw error;
        }
    }
}

async function signUpWithEmailAndPassword(page, authHost, postSignUpUrl, credentials, alreadyInSignupPage = false) {
    logger.info('Checking for user credentials');
    if (!credentials) {
        const timeStamp = Date.now();
        logger.info(`Generating credentials for test ${timeStamp}`);
        credentials = {
            email: `test+${timeStamp}@ibleducation.com`,
            password: 'test-password',
        };
    }
    if (!alreadyInSignupPage) {
        // Click on the sign up button
        const signUpButton = page.getByRole('button', { name: 'Sign Up' });
        await expect(signUpButton).toBeVisible({ timeout: 10000 });
        await signUpButton.click();
        // Wait for the sign up page to load
        await safeWaitForURL(page, (url) => url.href.startsWith(authHost + '/account/create'), {
            timeout: 60000,
        });
    }
    // Click on the continue with Password button
    const continueWithPasswordButton = page.getByRole('button', {
        name: 'continue with Password',
    });
    await expect(continueWithPasswordButton).toBeVisible({ timeout: 10000 });
    await continueWithPasswordButton.click();
    // Fill in Email field
    const emailInputField = page.getByPlaceholder('Email');
    await expect(emailInputField).toBeVisible();
    await emailInputField.fill(credentials.email);
    // Fill in Password field
    const passwordInputField = page.getByPlaceholder('Password', {
        exact: true,
    });
    await expect(passwordInputField).toBeVisible();
    await passwordInputField.fill(credentials.password);
    // Fill in Confirm Password field
    const confirmPasswordInputField = page.getByPlaceholder('Confirm Password');
    await expect(confirmPasswordInputField).toBeVisible();
    await confirmPasswordInputField.fill(credentials.password);
    // Click Create Account
    const createAccountButton = page.getByRole('button', {
        name: 'Create Account',
    });
    await expect(createAccountButton).toBeVisible();
    await createAccountButton.click();
    // Wait to be redirected back to application
    await safeWaitForURL(page, (url) => url.href.startsWith(postSignUpUrl), {
        timeout: 60000,
    });
    await page.waitForLoadState('domcontentloaded');
    await page.waitForTimeout(15000);
}
async function loginWithEmailAndPassword(page, username, password, hostUrl, postLoginUrlMatcher) {
    //NEW AUTH FLOW
    await page.click('button:has-text("Continue with Password")');
    logger.info('Waiting for 1 second before waiting for the email input');
    await page.waitForTimeout(1000);
    logger.info('Waiting for the password form to appear');
    await page.waitForSelector('input[type="email"]');
    logger.info('Filling in the login credentials');
    await page.fill('input[type="email"]', username);
    await page.fill('input[type="password"]', password);
    logger.info('Clicking the login button');
    await page.click('button:has-text("Continue")');
    await page.waitForTimeout(500);
    if (postLoginUrlMatcher) {
        await safeWaitForURL(page, (url) => postLoginUrlMatcher(url), {
            timeout: 80000,
        });
    }
}
async function loginWithMicrosoftIdp(page, username, password) {
    // Wait for SSO provider page to load
    await safeWaitForURL(page, /.*\/oauth2\/v2.0\/authorize.*/, {
        timeout: 60000,
    });
    await page.waitForLoadState('networkidle');
    const inputEmailEl = page.getByRole('textbox', { name: 'NetId@syr.edu' });
    await inputEmailEl.waitFor({ state: 'visible' });
    inputEmailEl.fill(username);
    const nextBtnEl = page.getByRole('button', { name: 'Next' });
    await nextBtnEl.waitFor({ state: 'visible' });
    nextBtnEl.click();
    const inputPasswordEl = page.getByPlaceholder('Password');
    await inputPasswordEl.waitFor({ state: 'visible' });
    inputPasswordEl.fill(password);
    const signinBtnEl = page.getByRole('button', { name: 'Sign in' });
    await signinBtnEl.waitFor({ state: 'visible' });
    signinBtnEl.click();
    const confirmStaySignIn = page.getByRole('button', { name: 'Yes' });
    await confirmStaySignIn.waitFor({ state: 'visible' });
    confirmStaySignIn.click();
}
async function canChatWithEmbedMentor(page, openChatButton) {
    await openChatButton.click();
    await page.waitForTimeout(20000);
    const widget = page.locator('#ibl-chat-widget-container');
    await expect(widget).toBeVisible();
    const mentorIframe = widget.frameLocator('iframe').nth(0);
    const navName = mentorIframe.locator('nav h1');
    await expect(navName).toBeVisible({ timeout: 15000 });
    const closeButton = mentorIframe.getByRole('button', {
        name: /close chat/i,
    });
    await expect(closeButton).toBeVisible();
    const img = mentorIframe.locator('img[data-slot="avatar-image"]').first();
    await expect(img).toBeVisible();
    await expect(img).toHaveAttribute('src');
    const text = 'Whats the context of this page';
    const textArea = mentorIframe.locator('textarea[placeholder]:not([placeholder=""])');
    await expect(textArea).toBeVisible();
    await expect(textArea).toHaveAttribute('placeholder', /.+/);
    const sendButton = mentorIframe.getByRole('button', {
        name: /send message/i,
    });
    await textArea.fill(text);
    await expect(sendButton).toBeEnabled();
    await sendButton.click();
    await page.waitForTimeout(5000);
    const userMessage = mentorIframe.locator('.chat-user-message-query', {
        hasText: text,
    });
    const mentorResponse = mentorIframe.locator('.chat-ai-message-response');
    await expect(userMessage).toBeVisible({ timeout: 10000 });
    await mentorResponse.waitFor({ state: 'visible' });
    await expect(mentorResponse).toBeVisible();
    await expect(mentorResponse).toContainText(/courses/i);
    // Check user message layout
    const userLayout = await userMessage.evaluate((el) => {
        const elem = el;
        return {
            scrollWidth: elem.scrollWidth,
            clientWidth: elem.clientWidth,
            height: elem.offsetHeight,
            overflows: elem.scrollWidth > elem.clientWidth,
        };
    });
    expect(userLayout.overflows).toBe(false);
    expect(userLayout.height).toBeGreaterThan(10);
    // Check mentor message layout
    const mentorLayout = await mentorResponse.evaluate((el) => {
        const elem = el;
        return {
            scrollWidth: elem.scrollWidth,
            clientWidth: elem.clientWidth,
            height: elem.offsetHeight,
            overflows: elem.scrollWidth > elem.clientWidth,
        };
    });
    expect(mentorLayout.overflows).toBe(false);
    expect(mentorLayout.height).toBeGreaterThan(10);
    await closeButton.click();
    await expect(widget).not.toBeVisible();
}
function getMentorIdFromUrl(url) {
    const { pathname } = new URL(url);
    const match = pathname.startsWith('/platform/');
    if (!match) {
        return null;
    }
    const parts = pathname.split('/').filter(Boolean); // ['platform', '<tenantKey>', '<mentorId>']
    return parts[2];
}

class AuthFlowBuilder {
    constructor(flow = 'username_password', idp = '', authHost = '') {
        this.flow = flow;
        this.idp = idp;
        this.authHost = authHost;
    }
    async run(page, step, username, password, hostUrl, authUrl, postLoginUrlMatcher) {
        switch (this.flow) {
            case 'username_password':
                return this.usernamePasswordFlow(page, username, password, hostUrl, authUrl, postLoginUrlMatcher);
            case 'sso':
                return this.ssoFlow(page, username, password, hostUrl, authUrl);
            case 'direct_sso':
                return this.directSsoFlow(page, step, username, password, hostUrl, authUrl);
            default:
                throw new Error(`Unknown auth flow: ${this.flow}`);
        }
    }
    // Username/password flow
    async usernamePasswordFlow(page, username, password, hostUrl, authUrl, postLoginUrlMatcher) {
        logger.info('Running username/password auth flow');
        return loginWithEmailAndPassword(page, username, password, hostUrl, postLoginUrlMatcher);
    }
    async magicLinkFlow(page) {
        logger.info('Magic link required for authentication');
        logger.info('Fill the email address field');
        await page.getByPlaceholder('Email').click();
        await page.getByPlaceholder('Email').fill('ibltestuser@mailsac.com');
        logger.info('Click the Continue button');
        await page.click('button:has-text("Continue")');
        await page.getByText(/Enter it below/i).waitFor({ timeout: 5000 });
        logger.info('Check for the magic link email');
        await page.waitForTimeout(5000);
        const apiKey = 'k_GZgb33koIUEPRRqciDzn6c21bZnNBit0GBQaV0a905hwwc832';
        const mailsac = new MailsacClient(apiKey);
        const emailInbox = 'ibltestuser';
        const emails = await mailsac.getEmails(emailInbox);
        const magicLinkEmail = emails.find((e) => e.subject === 'Verify your email to sign in for ibl');
        if (!magicLinkEmail) {
            throw new Error('Magic link email not found');
        }
        const emailContent = await mailsac.getEmailById(emailInbox, magicLinkEmail._id);
        const matches = emailContent.match(/\b\d{6}\b/);
        const signInCode = matches ? matches[0] : '';
        await page.keyboard.press(signInCode[0]);
        await page.keyboard.press(signInCode[1]);
        await page.keyboard.press(signInCode[2]);
        await page.keyboard.press(signInCode[3]);
        await page.keyboard.press(signInCode[4]);
        await page.keyboard.press(signInCode[5]);
    }
    // SSO flow (using platform's SSO IDP)
    async ssoFlow(page, username, password, hostUrl, authUrl) {
        logger.info(`Running SSO auth flow with IDP: ${this.idp}`);
        await page.goto(authUrl);
        await page.waitForLoadState('domcontentloaded');
        await page.click(`button:has-text("Continue with ${this.idp}")`);
        await page.waitForURL(/\/sso-login-complete\?data=.*/);
        await page.waitForLoadState('networkidle');
        logger.info('SSO flow completed');
    }
    // Direct SSO flow (using client-specific SSO IDP)
    async directSsoFlow(page, step, username, password, hostUrl, authUrl) {
        logger.info(`Running direct SSO auth flow with client IDP: ${this.idp}`);
        await page.goto(authUrl);
        await page.waitForLoadState('domcontentloaded');
        loginWithMicrosoftIdp(page, username, password);
        logger.info('Direct SSO flow completed');
    }
}

/**
 * Creates a Playwright setup test function for authentication.
 * Use this as the test body in your auth.setup.ts file:
 *
 * ```ts
 * import { test as setup } from '@iblai/iblai-js/playwright';
 * import { createAuthSetup } from '@iblai/iblai-js/playwright';
 *
 * setup('authenticate', createAuthSetup({
 *   hostUrl: process.env.MENTOR_NEXTJS_HOST || '',
 *   authHost: process.env.AUTH_HOST || '',
 *   appName: 'mentor',
 *   postLoginUrlMatcher: (url) => url.href.includes('/platform/'),
 * }));
 * ```
 */
function createAuthSetup(config) {
    return async ({ browser, step }, testInfo) => {
        const { hostUrl, authHost, appName, postLoginUrlMatcher, authFlow = process.env.AUTH_FLOW || 'username_password', authIdp = process.env.AUTH_IDP || '', } = config;
        // Get browser key from project name (e.g., 'setup-chrome' -> 'CHROME')
        const browserKey = testInfo.project.name.replace('setup-', '').toUpperCase();
        // Use browser-specific credentials with fallback to default
        const username = process.env[`PLAYWRIGHT_USERNAME_${browserKey}`] || process.env.PLAYWRIGHT_USERNAME || '';
        const password = process.env[`PLAYWRIGHT_PASSWORD_${browserKey}`] || process.env.PLAYWRIGHT_PASSWORD || '';
        // Use browser-specific auth file
        const authFile = `playwright/.auth/user-${browserKey.toLowerCase()}.json`;
        logger.info(`Authenticating for browser: ${browserKey} with user: ${username}`);
        const context = await browser.newContext({
            userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36',
        });
        test.setTimeout(200000); // 200 seconds
        const page = await context.newPage();
        await page.goto(hostUrl, { timeout: 60000, waitUntil: 'domcontentloaded' });
        await safeWaitForURL(page, (url) => url.href.includes(`/login?app=${appName}&redirect-to=${hostUrl}`), { timeout: 60000 });
        try {
            await page.waitForLoadState('domcontentloaded', { timeout: 60000 });
            await expect(page.getByRole('button', { name: 'Continue with Password' })).toBeVisible({
                timeout: 60000,
            });
        }
        catch (error) {
            logger.info(`############# Auth page load timeout: ${error.message}`);
        }
        logger.info('Authentication page loaded successfully');
        // Use AuthFlowBuilder for all flows
        const authFlowBuilder = new AuthFlowBuilder(authFlow, authIdp, authHost);
        await authFlowBuilder.run(page, step, username, password, hostUrl, authHost, postLoginUrlMatcher);
        try {
            logger.info('Waiting for url ', hostUrl);
            await safeWaitForURL(page, (url) => postLoginUrlMatcher(url), { timeout: 60000 });
            logger.info('After waiting for url ', hostUrl);
        }
        catch (e) {
            logger.info('Timeout waiting for URL: ', e.message + ' Continuing with tests');
        }
        const dmToken = await page.evaluate(() => localStorage.getItem('dm_token'));
        const axdToken = await page.evaluate(() => localStorage.getItem('axd_token'));
        expect(dmToken).not.toBeNull();
        expect(axdToken).not.toBeNull();
        await context.storageState({ path: authFile });
        logger.info('Authentication test completed successfully.');
        // Close page first, wait for stability, then close context with error handling
        // This prevents Chromium crashes (SIGSEGV) that can occur when closing context abruptly
        await page.waitForTimeout(500);
        await page.close();
        try {
            await context.close();
        }
        catch (e) {
            logger.info('Context close failed (may be Chromium crash), continuing...', e.message);
        }
    };
}

class CustomReporter {
    onBegin(config, suite) {
        if (suite.allTests().length === 0) {
            logger.info('No tests found');
            process.exit(0); // Exit gracefully when no tests are found
        }
        logger.info('Test run started');
    }
    onTestBegin(test) {
        logger.info(`Starting test: ${test.title}`);
    }
    onTestEnd(test, result) {
        var _a;
        logger.info(`Test ${test.title} finished with status: ${result.status}`);
        logger.info(`Test logs for ${test.title}:`);
        if (result.status === 'failed') {
            logger.info(`Test ${test.title} failed with error: ${(_a = result.error) === null || _a === void 0 ? void 0 : _a.message}`);
        }
    }
    onTimeout(timeout) {
        console.warn(`Timeout occurred: ${timeout}`);
    }
    onEnd(result) {
        logger.info(`Test run finished with status: ${result.status}`);
        logger.info('Overall logs:');
    }
    onStdOut(chunk, test, result) {
        logger.info(`[STDOUT] ${chunk.toString()}`);
    }
    onStdErr(chunk, test, result) {
        logger.error(`[STDERR] ${chunk.toString()}`);
    }
}

/**
 * Wait for a page to be fully ready (DOM complete + brief delay for lazy rendering)
 */
async function waitForPageReady(page, timeout = 30000) {
    await page.waitForFunction(() => document.readyState === 'complete', {
        timeout,
    });
    await page.waitForTimeout(2000);
}
/**
 * Check if the current user is an admin by reading localStorage
 */
async function checkAdminStatus(page) {
    try {
        await page.waitForFunction(() => {
            return window.localStorage.getItem('current_tenant') !== null;
        }, { timeout: 10000 });
        const isAdmin = await page.evaluate(() => {
            const raw = window.localStorage.getItem('current_tenant');
            if (!raw)
                return false;
            try {
                const parsed = JSON.parse(raw);
                return parsed.is_admin === true;
            }
            catch (_a) {
                return false;
            }
        });
        console.log('Admin status check result:', isAdmin);
        return isAdmin;
    }
    catch (error) {
        console.log('Error checking admin status:', error.message);
        return false;
    }
}
/**
 * Wait for an element to be fully stable (attached, visible, not animating)
 */
async function waitForElementStable(page, locator, timeout = 10000) {
    await locator.waitFor({ state: 'attached', timeout });
    await expect(locator).toBeVisible({ timeout });
    await page.waitForTimeout(500);
    return locator;
}
/**
 * Click an element with retry logic and stability checks
 */
async function reliableClick(page, locator, timeout = 10000, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await waitForElementStable(page, locator, timeout);
            await locator.scrollIntoViewIfNeeded();
            await page.waitForTimeout(200);
            await locator.click({ force: true });
            break;
        }
        catch (error) {
            console.log(`Click attempt ${attempt} failed:`, error.message);
            if (attempt === maxRetries) {
                throw new Error(`Failed to click element after ${maxRetries} attempts: ${error.message}`);
            }
            await page.waitForTimeout(1000);
        }
    }
}
/**
 * Fill an input element with retry logic, clearing first and verifying the value
 */
async function reliableFill(page, locator, value, timeout = 10000, maxRetries = 3) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            await waitForElementStable(page, locator, timeout);
            await locator.clear();
            await locator.fill(value);
            const actualValue = await locator.inputValue();
            if (actualValue !== value) {
                throw new Error(`Value mismatch: expected "${value}", got "${actualValue}"`);
            }
            break;
        }
        catch (error) {
            console.log(`Fill attempt ${attempt} failed:`, error.message);
            if (attempt === maxRetries) {
                throw new Error(`Failed to fill element after ${maxRetries} attempts: ${error.message}`);
            }
            await page.waitForTimeout(1000);
        }
    }
}
/**
 * Wait for a dialog element to be visible and its content loaded
 */
async function waitForDialogReady(page, dialogLocator, timeout = 15000) {
    await expect(dialogLocator).toBeVisible({ timeout });
    await page.waitForLoadState('domcontentloaded', { timeout });
    await page.waitForTimeout(1000);
    return dialogLocator;
}
/**
 * Select a date from a calendar component, navigating months if needed
 */
async function selectDateFromCalendar(page, dialogLocator, targetDate, timeout = 15000) {
    const dd = String(targetDate.getDate()).padStart(2, '0');
    const mm = String(targetDate.getMonth() + 1).padStart(2, '0');
    const yyyy = targetDate.getFullYear();
    const formattedDate = `${dd}/${mm}/${yyyy}`;
    console.log(`Attempting to select date: ${formattedDate}`);
    const calendar = dialogLocator.locator('[data-slot="calendar"]');
    await calendar.waitFor({ state: 'visible', timeout });
    await page.waitForTimeout(1000);
    const dateFormats = [
        formattedDate, // DD/MM/YYYY
        `${mm}/${dd}/${yyyy}`, // MM/DD/YYYY
        `${yyyy}-${mm}-${dd}`, // YYYY-MM-DD
    ];
    let dayButton = null;
    for (const format of dateFormats) {
        dayButton = dialogLocator.locator(`button[data-day="${format}"]:not([disabled])`);
        if (await dayButton.isVisible()) {
            console.log(`Found date with format: ${format}`);
            break;
        }
    }
    // If date not found, try navigating months
    if (!dayButton || !(await dayButton.isVisible())) {
        console.log('Date not in current month, attempting navigation...');
        let attempts = 0;
        const maxAttempts = 3;
        while (attempts < maxAttempts) {
            const nextMonthButton = dialogLocator.getByRole('button', {
                name: /next month/i,
            });
            if (await nextMonthButton.isVisible()) {
                await reliableClick(page, nextMonthButton);
                await page.waitForTimeout(1000);
                for (const format of dateFormats) {
                    dayButton = dialogLocator.locator(`button[data-day="${format}"]:not([disabled])`);
                    if (await dayButton.isVisible()) {
                        console.log(`Found date after navigation with format: ${format}`);
                        break;
                    }
                }
                if (dayButton && (await dayButton.isVisible())) {
                    break;
                }
            }
            else {
                console.log('Next month button not available');
                break;
            }
            attempts++;
        }
    }
    // Final verification and selection
    if (!dayButton || !(await dayButton.isVisible())) {
        const allDates = await dialogLocator.locator('button[data-day]').all();
        console.log('Available dates:');
        for (const date of allDates) {
            const dateAttr = await date.getAttribute('data-day');
            const disabled = await date.getAttribute('disabled');
            const visible = await date.isVisible();
            console.log({ date: dateAttr, disabled, visible });
        }
        throw new Error(`Could not find selectable date button for ${formattedDate}`);
    }
    await reliableClick(page, dayButton);
    console.log(`Successfully selected date: ${formattedDate}`);
}
/**
 * Run axe-core accessibility checks against a page
 */
async function expectNoAccessibilityViolations(page) {
    const results = await new AxeBuilder({ page })
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
        .options({
        rules: {
            'color-contrast': { enabled: false },
        },
    })
        .analyze();
    expect(results.violations).toEqual([]);
}
/**
 * Run axe-core accessibility checks against visible dialogs
 */
async function expectNoAccessibilityViolationsOnDialogs(page, rules, exclude = ['#embed-mentor-preview']) {
    const dialogLocator = page.locator('[role="dialog"]');
    try {
        await expect(dialogLocator.first()).toBeVisible({ timeout: 10000 });
    }
    catch (_a) {
        const pageUrl = page.url();
        const dialogCount = await dialogLocator.count();
        console.log(`[expectNoAccessibilityViolationsOnDialogs] Debug info:
      - Page URL: ${pageUrl}
      - Dialog count: ${dialogCount}`);
        throw new Error('expectNoAccessibilityViolationsOnDialogs: No visible element with [role="dialog"] found on page. ' +
            `Page URL: ${pageUrl}. Ensure the dialog is visible and has role="dialog" attribute.`);
    }
    const results = await new AxeBuilder({ page })
        .include('[role="dialog"]')
        .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
        .options({
        rules: {
            'color-contrast': { enabled: false },
            ...rules,
        },
    })
        .exclude(exclude)
        .analyze();
    expect(results.violations).toEqual([]);
}

const deferredNotificationText = 'You will be notified once the report is available.';
async function inviteUserTest(page, inviteModal) {
    const emailInput = inviteModal.locator('#email-invite');
    await expect(emailInput).toBeVisible({ timeout: 10000 });
    const uniqueEmail = `test+user+${Date.now()}@test.com`;
    await emailInput.fill(uniqueEmail);
    const submitBtn = inviteModal.getByRole('button', { name: 'Send Invite' });
    await expect(submitBtn).toBeEnabled({ timeout: 10000 });
    await submitBtn.click();
    await expect(submitBtn).toBeDisabled({ timeout: 15000 });
    await page.waitForLoadState('networkidle');
}
async function navigateToAccountComponent(page, profileBtn) {
    await profileBtn.waitFor({ state: 'visible' });
    await profileBtn.click();
    const profileMenuItem = page.getByRole('menuitem', { name: 'Profile' });
    await expect(profileMenuItem).toBeVisible();
    const tenantBtn = page.getByRole('menuitem').nth(1);
    await expect(tenantBtn).toBeVisible();
    await tenantBtn.click();
    const tenantDialog = page.getByRole('dialog').filter({ hasText: 'organization' });
    await expect(tenantDialog).toBeVisible();
    return tenantDialog;
}
/**
 * Click a report card's primary action and confirm the date-range picker, so callers
 * always end up in the post-generation state.
 *
 * Prefers the "Regenerate report" button when present (completed reports) because the plain
 * "Download report" button on a completed card re-downloads the existing URL and skips the
 * picker. When no Regenerate button exists, clicks "Download report" — which on a
 * non-completed card also opens the picker.
 *
 * Either way we wait for the picker and click Generate/Regenerate to start the report.
 */
async function triggerReportFromCard(page, card) {
    const regenerateButton = card.getByRole('button', { name: 'Regenerate report' });
    const downloadButton = card.getByRole('button', { name: 'Download report' });
    const hasRegenerate = await regenerateButton.isVisible().catch(() => false);
    const trigger = hasRegenerate ? regenerateButton : downloadButton;
    await trigger.click();
    const dateRangePopover = page.getByTestId('report-date-range-popover');
    await expect(dateRangePopover).toBeVisible({ timeout: 10000 });
    await dateRangePopover.getByRole('button', { name: /^(Generate|Regenerate)$/ }).click();
    await expect(dateRangePopover).not.toBeVisible({ timeout: 10000 });
}
async function navigateToDataReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    await safeWaitForURL(page, /\/analytics\/reports$/);
    logger.info('Successfully navigated to Data Reports page');
}
async function shouldDisplayReportCards(page, REPORT_CARDS) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const emptyState = page.getByText('No reports available yet.');
    const isEmptyState = await emptyState.isVisible().catch(() => false);
    if (isEmptyState) {
        await expect(emptyState).toBeVisible();
        logger.info('No reports available - empty state displayed');
        return;
    }
    for (const report of REPORT_CARDS) {
        const reportCard = page.getByLabel(report.ariaLabel);
        const isVisible = await reportCard.isVisible().catch(() => false);
        if (isVisible) {
            await expect(reportCard).toBeVisible();
            const heading = reportCard.getByRole('heading', {
                name: report.name,
                level: 3,
            });
            await expect(heading).toBeVisible();
            const downloadButton = reportCard.getByRole('button', {
                name: 'Download report',
            });
            await expect(downloadButton).toBeVisible();
            await expect(downloadButton).toBeEnabled();
            logger.info(`Report card "${report.name}" displayed correctly`);
        }
    }
}
async function shouldOpenCSVEditorDialog(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        logger.info('User Report card not visible - skipping test');
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    //expect Report generation processing...
    await expect(page.getByText('Report generation processing...')).toBeVisible({ timeout: 5000 });
    // After the processing toast appears, the backend either finishes in time (CSV editor opens)
    // or polling times out (deferred-notification toast appears). Treat both as valid outcomes.
    await waitForReportOutcomeOrCSVDialogOpen(page);
}
async function waitForReportOutcomeOrCSVDialogOpen(page) {
    const csvEditorDialog = page.getByRole('dialog', { name: 'Edit CSV Data' });
    const deferredNotification = page.getByText(deferredNotificationText);
    const downloadPromise = page.waitForEvent('download', { timeout: 60000 });
    const outcome = await Promise.race([
        csvEditorDialog
            .waitFor({ state: 'visible', timeout: 60000 })
            .then(() => 'csv-editor'),
        deferredNotification
            .waitFor({ state: 'visible', timeout: 60000 })
            .then(() => 'deferred'),
        downloadPromise.then(() => 'download'),
    ]).catch(() => null);
    if (outcome === 'deferred') {
        logger.info('Report generation deferred — user will be notified when available');
        return false;
    }
    if (outcome === 'download') {
        const download = await downloadPromise;
        const filename = download.suggestedFilename();
        expect(filename).toBe('report.csv');
        logger.info(`CSV file "${filename}" downloaded successfully`);
        return false;
    }
    if (outcome !== 'csv-editor') {
        throw new Error('Report generation did not complete: neither CSV editor nor deferred-notification toast appeared within 60s');
    }
    const dialogTitle = csvEditorDialog.getByRole('heading', {
        name: 'Edit CSV Data',
        level: 2,
    });
    await expect(dialogTitle).toBeVisible();
    const csvTable = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await expect(csvTable).toBeVisible();
    await expect(csvEditorDialog.getByRole('button', { name: 'Cancel' })).toBeVisible();
    await expect(csvEditorDialog.getByRole('button', { name: 'Save' })).toBeVisible();
    await expect(csvEditorDialog.getByRole('button', { name: 'Close' }).first()).toBeVisible();
    logger.info('CSV Editor dialog opened successfully');
    return csvEditorDialog;
}
async function shouldDisplayCSVInEditableTableFormat(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const table = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await expect(table).toBeVisible();
    const headerCells = table.locator('thead th');
    const headerCount = await headerCells.count();
    expect(headerCount).toBeGreaterThan(0);
    const dataRows = table.locator('tbody tr');
    const rowCount = await dataRows.count();
    expect(rowCount).toBeGreaterThan(0);
    logger.info(`CSV table has ${headerCount} columns and ${rowCount - 1} data rows (excluding Add Row)`);
}
async function shouldAllowEditingCellValuesInCSVEditor(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const table = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    const firstEditableCell = table.locator('tbody tr').first().locator('td').nth(1);
    const isCellVisible = await firstEditableCell.isVisible().catch(() => false);
    if (!isCellVisible) {
        logger.info('No editable cells found - skipping edit test');
        test$2.skip();
        return;
    }
    const initialButton = firstEditableCell.locator('button, div[role="button"]').first();
    const initialText = await initialButton.textContent();
    await initialButton.click();
    const cellInput = firstEditableCell.locator('input');
    await expect(cellInput).toBeVisible({ timeout: 5000 });
    const testValue = 'Edited Test Value';
    await cellInput.fill(testValue);
    await cellInput.press('Enter');
    await expect(firstEditableCell).toContainText(testValue);
    logger.info(`Successfully edited cell value from "${initialText}" to "${testValue}"`);
}
async function shouldAddNewRowWhenClickingAddRowButton(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const table = csvEditorDialog.locator('table');
    const initialRowCount = await table.locator('tbody tr').count();
    const addRowButton = csvEditorDialog.getByRole('button', {
        name: 'Add Row',
    });
    await expect(addRowButton).toBeVisible();
    await addRowButton.click();
    const newRowCount = await table.locator('tbody tr').count();
    expect(newRowCount).toBe(initialRowCount + 1);
    logger.info(`Added new row. Row count changed from ${initialRowCount} to ${newRowCount}`);
}
async function shouldSaveEditedCSVAndTriggerDownload(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const downloadPromise = page.waitForEvent('download', { timeout: 40000 });
    const saveButton = csvEditorDialog.getByRole('button', { name: 'Save' });
    await saveButton.click();
    const download = await downloadPromise;
    const filename = download.suggestedFilename();
    expect(filename).toBe('report.csv');
    await expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info(`CSV file "${filename}" downloaded successfully`);
}
async function shouldCloseCSVEditorWithoutSavingWhenClickingCancel(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const cancelButton = csvEditorDialog.getByRole('button', {
        name: 'Cancel',
    });
    await cancelButton.click();
    await expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('CSV Editor dialog closed via Cancel button');
}
async function shouldCloseCSVEditorWhenClickingCloseButton(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    await expect(userReportCard).toBeVisible({ timeout: 120000 });
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const closeButton = csvEditorDialog
        .getByRole('button', {
        name: 'Close',
    })
        .first();
    await closeButton.click();
    await expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('CSV Editor dialog closed via Close button');
}
async function shouldVerifyCSVEditorDialogAccessibility(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userReportCard = page.getByLabel('User Report report card');
    const isVisible = await userReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userReportCard);
    await page.waitForLoadState('networkidle');
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    await expect(csvEditorDialog).toHaveAttribute('role', 'dialog');
    const csvTable = csvEditorDialog.getByRole('table', {
        name: 'CSV data table',
    });
    await expect(csvTable).toBeVisible();
    const saveButton = csvEditorDialog.getByRole('button', {
        name: 'Save changes and download CSV',
    });
    await expect(saveButton).toBeVisible();
    const cancelButton = csvEditorDialog.getByRole('button', {
        name: 'Cancel editing and close dialog',
    });
    await expect(cancelButton).toBeVisible();
    const addRowButton = csvEditorDialog.getByRole('button', {
        name: 'Add Row',
    });
    await expect(addRowButton).toBeVisible();
    const editorRegion = csvEditorDialog.getByRole('region', {
        name: 'CSV data editor',
    });
    await expect(editorRegion).toBeVisible();
    logger.info('CSV Editor dialog has proper ARIA labels and roles');
}
async function shouldOpenCSVEditorForUserMetadataReport(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const userMetadataReportCard = page.getByLabel('User Metadata Report report card');
    const isVisible = await userMetadataReportCard.isVisible().catch(() => false);
    if (!isVisible) {
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, userMetadataReportCard);
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    const table = csvEditorDialog.locator('table');
    const companyHeader = table.locator('thead th input[value="company"]');
    const hasCompanyColumn = await companyHeader.isVisible().catch(() => false);
    if (hasCompanyColumn) {
        logger.info('User Metadata Report contains company column as expected');
    }
    await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
    await expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
}
async function shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const chatHistoryCard = page.getByLabel('All Mentor Chat History report card');
    if (!(await chatHistoryCard.isVisible().catch(() => false))) {
        test$2.skip();
        return;
    }
    // Prefer the Regenerate button (guaranteed to open the picker); fall back to Download.
    // Start listening for the download event before clicking since the auto-download fires
    // as soon as the picker is confirmed.
    const downloadPromise = page.waitForEvent('download', { timeout: 40000 });
    const triggerReportAsync = triggerReportFromCard(page, chatHistoryCard);
    // Either the browser fires a download within 40s, or the backend defers and shows the
    // "You will be notified once the report is available." toast. Both are valid outcomes.
    const deferredNotification = page.getByText(deferredNotificationText);
    const [, outcome] = await Promise.all([
        triggerReportAsync,
        Promise.race([
            downloadPromise.then((download) => ({ kind: 'download', download })),
            deferredNotification
                .waitFor({ state: 'visible', timeout: 40000 })
                .then(() => ({ kind: 'deferred' })),
        ]).catch(() => null),
    ]);
    if (!outcome) {
        throw new Error('Chat History generation did not complete: neither a download nor the deferred-notification toast appeared within 40s');
    }
    if (outcome.kind === 'download') {
        const filename = outcome.download.suggestedFilename();
        expect(filename.endsWith('.csv')).toBeTruthy();
        logger.info(`Chat History report downloaded: ${filename}`);
    }
    else {
        logger.info('Chat History generation deferred — user will be notified when available');
    }
    // Regardless of outcome, the CSV editor dialog should never have opened for chat history.
    const csvEditorDialog = page.getByRole('dialog', {
        name: 'Edit CSV Data',
    });
    const dialogOpened = await csvEditorDialog.isVisible().catch(() => false);
    expect(dialogOpened).toBeFalsy();
}
async function shouldDisableOtherDownloadButtonsWhileGeneratingReport(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 120000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const downloadButtons = page.getByRole('button', {
        name: 'Download report',
    });
    const buttonCount = await downloadButtons.count();
    if (buttonCount < 2) {
        logger.info('Not enough report cards to test disabled state - skipping');
        test$2.skip();
        return;
    }
    // Trigger generation on the first report card (preferring Regenerate when present so the
    // date-range picker always shows, then confirming it).
    const firstCard = page.getByLabel(/ report card$/).first();
    await triggerReportFromCard(page, firstCard);
    //slight timeout to wait for other download buttons to be disabled
    await page.waitForTimeout(500);
    const secondButton = downloadButtons.nth(1);
    const isSecondDisabled = await secondButton.isDisabled();
    if (isSecondDisabled) {
        logger.info('Other download buttons correctly disabled during report generation');
    }
    const csvEditorDialog = await waitForReportOutcomeOrCSVDialogOpen(page);
    if (!csvEditorDialog) {
        test$2.skip();
        return;
    }
    await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
}
// ============================================
// Combined Recommendation Reports Test Helpers
// ============================================
async function shouldShowCombiningReportsDialog(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - feature may not be enabled');
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, combinedReportCard.first());
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    await expect(combiningDialog).toBeVisible({ timeout: 40000 });
    await expect(combiningDialog.getByText('Combining Reports')).toBeVisible();
    await expect(combiningDialog.getByText('Loading & combining recommendations data')).toBeVisible();
    logger.info('Combining reports dialog displayed successfully');
}
async function shouldCancelCombiningReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - skipping');
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, combinedReportCard.first());
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    await expect(combiningDialog).toBeVisible({ timeout: 40000 });
    const cancelButton = page.locator('[data-testid="cancel-combining-button"]');
    await expect(cancelButton).toBeVisible();
    await cancelButton.click();
    await expect(combiningDialog).not.toBeVisible({ timeout: 5000 });
    logger.info('Successfully cancelled combining reports');
}
async function shouldHaveCombinedReportDataTestIds(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCards = page.locator('[data-testid="combined-recommendation-report-card"]');
    const count = await combinedReportCards.count();
    if (count === 0) {
        logger.info('No combined recommendation report cards found - feature may not be enabled');
        test$2.skip();
        return;
    }
    for (let i = 0; i < count; i++) {
        const card = combinedReportCards.nth(i);
        await expect(card).toBeVisible();
        await expect(card.getByRole('button', { name: 'Download report' })).toBeVisible();
    }
    logger.info(`Found ${count} report cards with combined recommendation data-testid`);
}
async function shouldCombineRecommendationReports(page) {
    const dataReportsTab = page.getByRole('tab', { name: 'Data Reports' });
    await expect(dataReportsTab).toBeVisible({ timeout: 60000 });
    await dataReportsTab.click();
    await waitForPageReady(page);
    const combinedReportCard = page.locator('[data-testid="combined-recommendation-report-card"]');
    const isVisible = await combinedReportCard
        .first()
        .isVisible()
        .catch(() => false);
    if (!isVisible) {
        logger.info('No combined recommendation report cards found - skipping');
        test$2.skip();
        return;
    }
    await triggerReportFromCard(page, combinedReportCard.first());
    const combiningDialog = page.locator('[data-testid="combining-reports-dialog"]');
    const dialogOrEditor = await Promise.race([
        combiningDialog.waitFor({ state: 'visible', timeout: 10000 }).then(() => 'dialog'),
        page
            .getByRole('dialog', { name: 'Edit CSV Data' })
            .waitFor({ state: 'visible', timeout: 60000 })
            .then(() => 'editor'),
    ]).catch(() => null);
    if (dialogOrEditor === 'dialog') {
        await expect(combiningDialog).not.toBeVisible({ timeout: 120000 });
    }
    const csvEditorDialog = page.getByRole('dialog', { name: 'Edit CSV Data' });
    await expect(csvEditorDialog).toBeVisible({ timeout: 120000 });
    const table = csvEditorDialog.getByRole('table', { name: 'CSV data table' });
    await expect(table).toBeVisible();
    const headerCells = table.locator('thead th');
    const headerCount = await headerCells.count();
    expect(headerCount).toBeGreaterThan(0);
    logger.info(`Combined reports opened in CSV editor with ${headerCount} columns`);
    await csvEditorDialog.getByRole('button', { name: 'Close' }).first().click();
    await expect(csvEditorDialog).not.toBeVisible({ timeout: 5000 });
}

/**
 * Create an environment configuration object from process.env with optional overrides.
 * Each app can call this with their specific defaults.
 */
function createEnvConfig(overrides) {
    return {
        skillHost: process.env.SKILLS_HOST || '',
        authHost: process.env.AUTH_HOST || '',
        analyticsHost: process.env.ANALYTICS_HOST || '',
        mentorHost: process.env.MENTOR_HOST || '',
        mentorNextjsHost: process.env.MENTOR_NEXTJS_HOST || '',
        authNextjsHost: process.env.AUTH_NEXTJS_HOST || '',
        playwrightUsername: process.env.PLAYWRIGHT_USERNAME || '',
        playwrightPassword: process.env.PLAYWRIGHT_PASSWORD || '',
        inviteUsername: process.env.INVITE_USERNAME || '',
        inviteUserPassword: process.env.INVITE_USER_PASSWORD || '',
        authFlow: process.env.AUTH_FLOW || 'username_password',
        authIdp: process.env.AUTH_IDP || '',
        enableStripeTest: process.env.ENABLE_STRIPE_TEST === 'true',
        iblMentorDisabledTabs: process.env.IBL_MENTOR_DISABLED_TABS || '',
        embedUrl: process.env.EMBED_URL || 'https://conradmugabe.vercel.app/',
        wcagRange: process.env.WCAG_RANGE || '',
        ...overrides,
    };
}

/**
 * Build the expected report download page URL.
 * Format: <baseUrl>/reports/<platformKey>/<reportName>
 */
function buildReportUrl(baseUrl, platformKey, reportName) {
    const base = baseUrl.replace(/\/$/, '');
    return `${base}/reports/${platformKey}/${reportName}`;
}
/**
 * Parse platform_key and report_name from the current page URL.
 * Expected format: .../reports/<platform_key>/<report_name>
 */
function parseReportUrlParams(url) {
    const match = url.match(/\/reports\/([^/]+)\/([^/]+)/);
    if (!match)
        return null;
    return { platformKey: match[1], reportName: match[2] };
}
/**
 * Navigate to the report download page and verify the URL matches the expected format.
 */
async function navigateToReportDownload(page, options) {
    const { baseUrl, platformKey, reportName, timeout = 60000 } = options;
    const url = buildReportUrl(baseUrl, platformKey, reportName);
    logger.info(`Navigating to report download page: ${url}`);
    await page.goto(url);
    await page.waitForLoadState('domcontentloaded', { timeout });
    // Verify URL matches expected format
    expect(page.url()).toContain(`/reports/${platformKey}/${reportName}`);
}
/**
 * Verify the report download page is showing the "Preparing" phase.
 */
async function verifyPreparingPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 10000;
    logger.info('Verifying report download page is in preparing phase');
    await expect(page.getByText('Preparing your report')).toBeVisible({ timeout });
    await expect(page.getByText('Fetching report details, this will only take a moment...')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "downloading" phase.
 */
async function verifyDownloadingPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Waiting for report download to start');
    await expect(page.getByText('Report download started')).toBeVisible({ timeout });
    await expect(page.getByText('Your download should begin automatically.')).toBeVisible({
        timeout,
    });
    await expect(page.getByText('Click here to download if not started')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "done" phase.
 */
async function verifyDonePhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Waiting for report download to complete');
    await expect(page.getByText('Report downloaded')).toBeVisible({ timeout });
    await expect(page.getByText('Your report has been downloaded successfully.')).toBeVisible({
        timeout,
    });
    await expect(page.getByText('Download Again')).toBeVisible({ timeout });
}
/**
 * Wait for and verify the report transitions to the "error" phase.
 */
async function verifyErrorPhase(page, options) {
    var _a;
    const timeout = (_a = options === null || options === void 0 ? void 0 : options.timeout) !== null && _a !== void 0 ? _a : 60000;
    logger.info('Verifying report download error state');
    await expect(page.getByText('Download failed')).toBeVisible({ timeout });
    await expect(page.getByText('Something went wrong. Please try again later.')).toBeVisible({
        timeout,
    });
}
/**
 * Verify the "Back Home" button is visible and click it.
 */
async function clickBackHome(page) {
    logger.info('Clicking Back Home button');
    const backButton = page.getByRole('button', { name: 'Back Home' });
    await expect(backButton).toBeVisible();
    await backButton.click();
}
/**
 * Click the "Download Again" button (only visible in done phase).
 * Skips silently if the button is not visible.
 */
async function clickDownloadAgain(page) {
    logger.info('Clicking Download Again button');
    const button = page.getByRole('button', { name: 'Download Again' });
    const isVisible = await button.isVisible().catch(() => false);
    if (!isVisible) {
        logger.info('Download Again button not visible, skipping');
        return false;
    }
    await button.click();
    return true;
}
/**
 * Click the manual download link (only visible in downloading phase).
 */
async function clickManualDownloadLink(page) {
    logger.info('Clicking manual download link');
    const link = page.getByText('Click here to download if not started');
    await expect(link).toBeVisible();
    await link.click();
}
/**
 * Full flow: navigate to report download page and wait for download to complete.
 * Falls back to verifying the error phase if the download does not succeed.
 * Returns the resulting phase: 'done' or 'error'.
 */
async function waitForReportDownload(page, options) {
    var _a;
    await navigateToReportDownload(page, options);
    await verifyPreparingPhase(page);
    const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 60000;
    // Wait for either the done or error phase to appear
    const result = await Promise.race([
        page
            .getByText('Report downloaded')
            .waitFor({ state: 'visible', timeout })
            .then(() => 'done'),
        page
            .getByText('Download failed')
            .waitFor({ state: 'visible', timeout })
            .then(() => 'error'),
    ]);
    if (result === 'done') {
        logger.info('Report download completed successfully');
        await verifyDonePhase(page, { timeout: 5000 });
    }
    else {
        logger.warn('Report download failed, verifying error state');
        await verifyErrorPhase(page, { timeout: 5000 });
    }
    return result;
}

/**
 * Check if the Memory tab is visible in the profile modal.
 * Returns true if the tab exists and is visible, false otherwise.
 */
async function isMemoryTabVisible(page) {
    const memoryTab = page.getByRole('tab', { name: 'Memory', exact: true });
    try {
        await expect(memoryTab).toBeVisible({ timeout: 5000 });
        return true;
    }
    catch (_a) {
        return false;
    }
}
/**
 * Switch to the Memory tab in the profile modal.
 * Assumes the profile modal is already open and the Memory tab is visible.
 */
async function switchToMemoryTab(page) {
    const tabButton = page.getByRole('tab', { name: 'Memory', exact: true });
    await expect(tabButton).toBeVisible({ timeout: 10000 });
    await tabButton.click();
}
/**
 * Verify that Memory tab settings section is visible with both toggles.
 */
async function verifyMemoryTabSettings(page) {
    await expect(page.getByText('Memory & Personalization')).toBeVisible({ timeout: 10000 });
    // Both switch labels should be visible
    await expect(page.getByText('Allow AI to learn from our conversations')).toBeVisible();
    await expect(page.getByText('Use my saved information in responses')).toBeVisible();
}
/**
 * Verify that the "My Memories" section is visible with Add Memory button.
 */
async function verifyMemoryTabMemoriesList(page) {
    await expect(page.getByText('My Memories')).toBeVisible({ timeout: 10000 });
    await expect(page.getByRole('button', { name: /Add Memory/i })).toBeVisible();
}
/**
 * Open the Add Memory dialog from the Memory tab.
 * Assumes the Memory tab is active.
 */
async function openAddMemoryDialog(page) {
    const addMemoryButton = page.getByRole('button', { name: /Add Memory/i });
    await expect(addMemoryButton).toBeVisible({ timeout: 10000 });
    await addMemoryButton.click();
    const dialog = page.getByRole('dialog').filter({ hasText: 'Add Memory' });
    await expect(dialog).toBeVisible({ timeout: 5000 });
    return dialog;
}
/**
 * Toggle a memory setting switch and verify the state changes.
 * Returns the new checked state.
 */
async function toggleMemorySwitch(page, switchName) {
    const switchEl = page.getByRole('switch', { name: switchName });
    await expect(switchEl).toBeVisible({ timeout: 10000 });
    const wasChecked = await switchEl.isChecked();
    await switchEl.click();
    // Wait for API response
    await page.waitForTimeout(1000);
    const isNowChecked = await switchEl.isChecked();
    logger.info(`Toggled "${switchName}" from ${wasChecked} to ${isNowChecked}`);
    return isNowChecked;
}
/**
 * Add a memory via the Add Memory dialog.
 * Opens the dialog, fills content, saves, and waits for dialog to close.
 */
async function addMemory(page, content) {
    await openAddMemoryDialog(page);
    const textarea = page.locator('#memory-content');
    await textarea.fill(content);
    const saveButton = page.getByRole('button', { name: 'Save Memory' });
    await expect(saveButton).toBeEnabled();
    await saveButton.click();
    // Wait for dialog to close (indicates success)
    await expect(page.getByRole('heading', { name: 'Add Memory' })).not.toBeVisible({
        timeout: 10000,
    });
    logger.info(`Added memory: "${content}"`);
}
/**
 * Delete the first visible memory in the list.
 * Hovers to reveal the delete button, then clicks it.
 */
async function deleteFirstMemory(page) {
    const deleteButton = page.locator('button[aria-label^="Delete memory:"]').first();
    await expect(deleteButton).toBeVisible({ timeout: 5000 });
    await deleteButton.hover();
    await deleteButton.click();
    // Wait for delete action to complete
    await page.waitForTimeout(2000);
    logger.info('Memory deleted successfully');
}
/**
 * Delete a specific memory by matching its content text.
 * Finds the memory item containing the text, hovers to reveal
 * the delete button, and clicks it.
 */
async function deleteMemoryByContent(page, content) {
    // Find the memory row containing this text
    const memoryRow = page.locator('.group').filter({ hasText: content }).first();
    await expect(memoryRow).toBeVisible({ timeout: 5000 });
    const deleteButton = memoryRow.locator('button[aria-label^="Delete memory:"]');
    await deleteButton.hover();
    await deleteButton.click();
    // Wait for delete to complete and item to be removed
    await expect(memoryRow.getByText(content)).not.toBeVisible({ timeout: 10000 });
    logger.info(`Deleted memory: "${content}"`);
}
/**
 * @deprecated Use `deleteFirstMemory` instead. Archive was renamed to Delete.
 */
const archiveFirstMemory = deleteFirstMemory;
/**
 * @deprecated Use `deleteMemoryByContent` instead. Archive was renamed to Delete.
 */
const archiveMemoryByContent = deleteMemoryByContent;
/**
 * Get the count of visible memory items in the list.
 * Returns 0 if the empty state is shown.
 */
async function getMemoryCount(page) {
    const emptyState = page.getByText('No memories yet. The AI will start remembering');
    const isEmpty = await emptyState.isVisible().catch(() => false);
    if (isEmpty)
        return 0;
    const memoryItems = page.locator('[aria-label="Auto-generated memory"], [aria-label="Manually added memory"]');
    return memoryItems.count();
}
/**
 * Verify that a memory with the given content text exists in the list.
 */
async function verifyMemoryExists(page, content) {
    await expect(page.getByText(content)).toBeVisible({ timeout: 10000 });
    logger.info(`Verified memory exists: "${content}"`);
}
/**
 * Verify that a memory with the given content text does NOT exist in the list.
 */
async function verifyMemoryNotExists(page, content) {
    await expect(page.getByText(content)).not.toBeVisible({ timeout: 5000 });
    logger.info(`Verified memory removed: "${content}"`);
}

// ============================
// Navigation Helpers
// ============================
/**
 * Navigate to the Audit tab within the Analytics section.
 * Assumes the user is already on the analytics page (any tab).
 */
async function navigateToAuditLog(page) {
    const auditLogTab = page.getByRole('tab', { name: 'Audit' });
    await expect(auditLogTab).toBeVisible({ timeout: 60000 });
    await auditLogTab.click();
    await waitForPageReady(page);
    await safeWaitForURL(page, /\/analytics\/audit$/);
    logger.info('Successfully navigated to Audit page');
}
// ============================
// Table Verification Helpers
// ============================
/**
 * Verify the audit log table is visible with correct headers.
 */
async function verifyAuditLogTableVisible(page) {
    const table = page.locator('table');
    await expect(table).toBeVisible({ timeout: 30000 });
    await expect(table.locator('th').filter({ hasText: 'Person' })).toBeVisible();
    await expect(table.locator('th').filter({ hasText: 'Action' })).toBeVisible();
    await expect(table.locator('th').filter({ hasText: 'Timestamp' })).toBeVisible();
    logger.info('Audit log table is visible with correct headers');
}
/**
 * Get the number of rows currently displayed in the audit log table.
 * Returns 0 if no table body rows are found.
 */
async function getAuditLogRowCount(page) {
    const rows = page.locator('table tbody tr');
    const count = await rows.count();
    logger.info(`Audit log table has ${count} rows`);
    return count;
}
/**
 * Verify that audit log entries contain expected data structure
 * (person name in first cell, action description in second, timestamp in third).
 */
async function verifyAuditLogEntryStructure(page) {
    const firstRow = page.locator('table tbody tr').first();
    await expect(firstRow).toBeVisible({ timeout: 30000 });
    const cells = firstRow.locator('td');
    const cellCount = await cells.count();
    expect(cellCount).toBe(3);
    // Person cell should have text content
    const personText = await cells.nth(0).textContent();
    expect(personText === null || personText === void 0 ? void 0 : personText.trim().length).toBeGreaterThan(0);
    // Action cell should have text content
    const actionText = await cells.nth(1).textContent();
    expect(actionText === null || actionText === void 0 ? void 0 : actionText.trim().length).toBeGreaterThan(0);
    // Timestamp cell should have text content
    const timestampText = await cells.nth(2).textContent();
    expect(timestampText === null || timestampText === void 0 ? void 0 : timestampText.trim().length).toBeGreaterThan(0);
    logger.info(`Audit log entry: Person="${personText === null || personText === void 0 ? void 0 : personText.trim()}", Action="${actionText === null || actionText === void 0 ? void 0 : actionText.trim()}", Timestamp="${timestampText === null || timestampText === void 0 ? void 0 : timestampText.trim()}"`);
}
// ============================
// Empty & Error State Helpers
// ============================
/**
 * Verify the empty state is displayed when there are no audit log entries.
 */
async function verifyAuditLogEmptyState(page) {
    const emptyMessage = page.getByText('No audit log entries found for this tenant.');
    await expect(emptyMessage).toBeVisible({ timeout: 30000 });
    logger.info('Audit log empty state is displayed');
}
/**
 * Verify the 403 permission error state is displayed.
 */
async function verifyAuditLogPermissionError(page) {
    const errorMessage = page.getByText('You do not have permission to view audit logs.');
    await expect(errorMessage).toBeVisible({ timeout: 30000 });
    logger.info('Audit log permission error is displayed');
}
/**
 * Verify the generic error state is displayed.
 */
async function verifyAuditLogGenericError(page) {
    const errorMessage = page.getByText('An error occurred while loading audit logs.');
    await expect(errorMessage).toBeVisible({ timeout: 30000 });
    logger.info('Audit log generic error is displayed');
}
/**
 * Verify the loading spinner is displayed while audit logs are being fetched.
 */
async function verifyAuditLogLoading(page) {
    const spinner = page.locator('.animate-spin');
    await expect(spinner).toBeVisible({ timeout: 10000 });
    logger.info('Audit log loading spinner is displayed');
}
/**
 * Wait for the audit log data to finish loading.
 * Waits for either table data, empty state, or error state to appear.
 */
async function waitForAuditLogDataLoaded(page, timeout = 60000) {
    await page
        .locator('table tbody tr, [class*="empty"], [class*="error"]')
        .first()
        .waitFor({ state: 'visible', timeout })
        .catch(() => {
        // Also check for known text states
    });
    // Wait for spinner to disappear
    const spinner = page.locator('.animate-spin');
    const isSpinnerVisible = await spinner.isVisible().catch(() => false);
    if (isSpinnerVisible) {
        await expect(spinner).not.toBeVisible({ timeout });
    }
    logger.info('Audit log data has finished loading');
}
// ============================
// Filter Interaction Helpers
// ============================
/**
 * Filter audit log entries by action type.
 * @param action - One of 'All Actions', 'Create', 'Update', or 'Delete'
 */
async function filterByAction(page, action) {
    const selectTrigger = page.locator('[role="combobox"][dir="ltr"]');
    await expect(selectTrigger).toBeVisible({ timeout: 10000 });
    await selectTrigger.click();
    const option = page.getByRole('option', { name: action });
    await expect(option).toBeVisible({ timeout: 5000 });
    await option.click();
    await waitForAuditLogDataLoaded(page);
    logger.info(`Filtered audit log by action: ${action}`);
}
/**
 * Filter audit log entries by actor/person using the search combobox.
 * @param actorName - The username to filter by, or empty string to clear the filter
 */
async function filterByActor(page, actorName) {
    const actorButton = page.getByLabel('Search for User');
    await expect(actorButton).toBeVisible({ timeout: 10000 });
    await actorButton.click();
    // Wait for the command popover to open
    const searchInput = page.getByPlaceholder('Search user...');
    await expect(searchInput).toBeVisible({ timeout: 5000 });
    if (actorName) {
        await searchInput.fill(actorName);
        // Select the matching actor from the dropdown
        const actorItem = page.locator('[cmdk-item]').filter({ hasText: actorName });
        await expect(actorItem).toBeVisible({ timeout: 5000 });
        await actorItem.click();
    }
    else {
        // Select "All Users" to clear the filter
        const allPeopleItem = page.locator('[cmdk-item]').filter({ hasText: 'All Users' });
        await expect(allPeopleItem).toBeVisible({ timeout: 5000 });
        await allPeopleItem.click();
    }
    await waitForAuditLogDataLoaded(page);
    logger.info(`Filtered audit log by actor: ${actorName || 'All Users'}`);
}
/**
 * Get the list of actors available in the actor filter dropdown.
 * Opens the dropdown, reads the options, then closes it.
 */
async function getAvailableActors(page) {
    const actorButton = page.getByLabel('Search for User');
    await expect(actorButton).toBeVisible({ timeout: 10000 });
    await actorButton.click();
    // Wait for the command popover
    await expect(page.getByPlaceholder('Search user...')).toBeVisible({ timeout: 5000 });
    const actorItems = page.locator('[cmdk-item] .font-medium.text-gray-700');
    const count = await actorItems.count();
    const actors = [];
    for (let i = 0; i < count; i++) {
        const text = await actorItems.nth(i).textContent();
        if (text)
            actors.push(text.trim());
    }
    // Close the dropdown by pressing Escape
    await page.keyboard.press('Escape');
    logger.info(`Available actors: ${actors.join(', ')}`);
    return actors;
}
/**
 * Open the date range picker and select a date range.
 * @param fromDate - Start date to select
 * @param toDate - End date to select
 */
async function filterByDateRange(page, fromDate, toDate) {
    const dateButton = page.getByRole('button', {
        name: /Pick a Date Range|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/,
    });
    await expect(dateButton).toBeVisible({ timeout: 10000 });
    await dateButton.click();
    // Wait for calendar to appear
    const calendar = page.locator('[role="grid"]').first();
    await expect(calendar).toBeVisible({ timeout: 5000 });
    // Select the from date
    const fromDay = String(fromDate.getDate());
    const fromButton = calendar.getByRole('gridcell', { name: fromDay, exact: true }).first();
    await fromButton.click();
    // Select the to date
    const toDay = String(toDate.getDate());
    const toButton = calendar.getByRole('gridcell', { name: toDay, exact: true }).first();
    await toButton.click();
    // Close by clicking outside
    await page.locator('body').click({ position: { x: 0, y: 0 } });
    await waitForAuditLogDataLoaded(page);
    logger.info(`Filtered audit log by date range: ${fromDate.toISOString().split('T')[0]} to ${toDate.toISOString().split('T')[0]}`);
}
/**
 * Clear the date range filter by clicking the date picker and resetting.
 */
async function clearDateRangeFilter(page) {
    const dateButton = page.getByRole('button', { name: /Pick a Date Range/ });
    // If button shows "Pick a Date Range", filter is already cleared
    const isCleared = await dateButton.isVisible().catch(() => false);
    if (isCleared) {
        logger.info('Date range filter is already cleared');
        return;
    }
    logger.info('Date range filter cleared');
}
// ============================
// Pagination Helpers
// ============================
/**
 * Get the current pagination info text (e.g., "Showing 1 to 20 of 45 results").
 * Returns null if pagination is not visible.
 */
async function getPaginationInfo(page) {
    var _a;
    const paginationText = page.locator('.text-sm.text-gray-700');
    const isVisible = await paginationText.isVisible().catch(() => false);
    if (!isVisible) {
        logger.info('Pagination info is not visible (likely single page)');
        return null;
    }
    const text = await paginationText.textContent();
    logger.info(`Pagination info: ${text === null || text === void 0 ? void 0 : text.trim()}`);
    return (_a = text === null || text === void 0 ? void 0 : text.trim()) !== null && _a !== void 0 ? _a : null;
}
/**
 * Navigate to the next page of audit log results.
 */
async function goToNextPage(page) {
    const nextLink = page.getByRole('link', { name: 'Go to next page' });
    await expect(nextLink).toBeVisible({ timeout: 5000 });
    await nextLink.click();
    await waitForAuditLogDataLoaded(page);
    logger.info('Navigated to next page');
}
/**
 * Navigate to the previous page of audit log results.
 */
async function goToPreviousPage(page) {
    const prevLink = page.getByRole('link', { name: 'Go to previous page' });
    await expect(prevLink).toBeVisible({ timeout: 5000 });
    await prevLink.click();
    await waitForAuditLogDataLoaded(page);
    logger.info('Navigated to previous page');
}
/**
 * Navigate to the first page of audit log results.
 */
async function goToFirstPage(page) {
    await goToPage(page, 1);
    logger.info('Navigated to first page');
}
/**
 * Navigate to the last page of audit log results.
 * Finds the highest numbered page link in the pagination.
 */
async function goToLastPage(page) {
    const paginationNav = page.getByRole('navigation', { name: 'pagination' });
    await expect(paginationNav).toBeVisible({ timeout: 5000 });
    const pageLinks = paginationNav.getByRole('link');
    const count = await pageLinks.count();
    // The last numeric link before "Next" is the last page
    let lastPageNum = 1;
    for (let i = 0; i < count; i++) {
        const text = await pageLinks.nth(i).textContent();
        const num = Number(text === null || text === void 0 ? void 0 : text.trim());
        if (!isNaN(num) && num > lastPageNum) {
            lastPageNum = num;
        }
    }
    await goToPage(page, lastPageNum);
    logger.info(`Navigated to last page (${lastPageNum})`);
}
/**
 * Navigate to a specific page number by clicking its page button.
 */
async function goToPage(page, pageNumber) {
    const pageLink = page
        .getByRole('navigation', { name: 'pagination' })
        .getByRole('link', { name: String(pageNumber), exact: true });
    await expect(pageLink).toBeVisible({ timeout: 5000 });
    await pageLink.click();
    await waitForAuditLogDataLoaded(page);
    logger.info(`Navigated to page ${pageNumber}`);
}
/**
 * Verify the current page is highlighted/active in pagination.
 */
async function verifyCurrentPage(page, expectedPage) {
    const pageLink = page
        .getByRole('navigation', { name: 'pagination' })
        .getByRole('link', { name: String(expectedPage), exact: true });
    await expect(pageLink).toBeVisible({ timeout: 5000 });
    await expect(pageLink).toHaveClass(/bg-blue-500/);
    logger.info(`Verified current page is ${expectedPage}`);
}
/**
 * Check if the next page button is disabled (i.e., we're on the last page).
 */
async function isOnLastPage(page) {
    const nextLink = page.getByRole('link', { name: 'Go to next page' });
    const hasDisabledClass = await nextLink
        .evaluate((el) => el.classList.contains('pointer-events-none'))
        .catch(() => true);
    return hasDisabledClass;
}
/**
 * Check if the previous page button is disabled (i.e., we're on the first page).
 */
async function isOnFirstPage(page) {
    const prevLink = page.getByRole('link', { name: 'Go to previous page' });
    const hasDisabledClass = await prevLink
        .evaluate((el) => el.classList.contains('pointer-events-none'))
        .catch(() => true);
    return hasDisabledClass;
}
// ============================
// Combined Workflow Helpers
// ============================
/**
 * Full workflow: Navigate to audit log tab, wait for data to load,
 * and verify the table is visible. Use this as a test setup helper.
 */
async function navigateToAuditLogAndWaitForData(page) {
    await navigateToAuditLog(page);
    await waitForAuditLogDataLoaded(page);
    logger.info('Audit log page ready with data loaded');
}
/**
 * Verify audit log entries are displayed after applying action filter.
 * Returns the row count after filtering.
 */
async function filterByActionAndVerify(page, action) {
    await filterByAction(page, action);
    const count = await getAuditLogRowCount(page);
    logger.info(`After filtering by "${action}": ${count} entries found`);
    return count;
}
/**
 * Verify audit log entries are displayed after applying actor filter.
 * Returns the row count after filtering.
 */
async function filterByActorAndVerify(page, actorName) {
    await filterByActor(page, actorName);
    const count = await getAuditLogRowCount(page);
    logger.info(`After filtering by actor "${actorName}": ${count} entries found`);
    return count;
}

/** Extract browser key from device name (e.g., 'Desktop Chrome' -> 'chrome') */
function getBrowserKey(deviceName) {
    return deviceName.toLowerCase().replace(/^desktop\s+/, '');
}
/**
 * Generate per-browser setup projects for Playwright authentication.
 * Each browser gets its own auth setup project (setup-chrome, setup-firefox, etc.)
 */
function generateBrowserSetupProjects(devicesToUse) {
    return devicesToUse.map((device) => {
        const browserKey = getBrowserKey(device);
        return {
            name: `setup-${browserKey}`,
            testMatch: /auth\.setup\.ts/,
            use: {
                ...devices[device],
            },
        };
    });
}
/**
 * Generate a Playwright project config for a platform + device combination.
 */
function generateProjectConfig(platform, deviceName, dependencies = [], otherTestMatch = ['*.common.spec.ts'], testIgnoreMatch = []) {
    const deviceSuffix = deviceName.toLowerCase().replace(/\s+/g, '-');
    const browserKey = getBrowserKey(deviceName);
    // Map generic 'setup' dependency to browser-specific setup
    const updatedDependencies = dependencies.map((dep) => dep === 'setup' ? `setup-${browserKey}` : dep);
    return {
        name: `${platform}-${deviceSuffix}`,
        testMatch: [`**${platform}/*.spec.ts`, ...otherTestMatch],
        use: {
            ...devices[deviceName],
            storageState: `playwright/.auth/user-${browserKey}.json`,
        },
        dependencies: [...updatedDependencies],
        testIgnore: [...testIgnoreMatch],
    };
}
/**
 * Create a complete Playwright configuration for an IBL.ai application.
 *
 * Usage:
 * ```ts
 * import { createPlaywrightConfig } from '@iblai/iblai-js/playwright';
 *
 * export default createPlaywrightConfig({
 *   platforms: [
 *     { name: 'skills', dependencies: ['setup'], otherTestMatch: ['**skills/*\/*.spec.ts'] },
 *   ],
 * });
 * ```
 */
function createPlaywrightConfig(options) {
    const { testDir = './tests', devices: devicesToUse = ['Desktop Chrome', 'Desktop Firefox', 'Desktop Safari', 'Desktop Edge'], platforms, customReporterPath = './custom-reporter', extraProjects = [], } = options;
    const browserSetupProjects = generateBrowserSetupProjects(devicesToUse);
    const platformProjects = [];
    for (const platform of platforms) {
        const platformDevices = platform.devices || devicesToUse;
        for (const device of platformDevices) {
            platformProjects.push(generateProjectConfig(platform.name, device, platform.dependencies || [], platform.otherTestMatch || ['*.common.spec.ts'], platform.testIgnore || []));
        }
    }
    // Generate extra projects (cleanup, public-views, etc.)
    const extraProjectConfigs = [];
    for (const extra of extraProjects) {
        const extraDevices = extra.devices || devicesToUse;
        for (const device of extraDevices) {
            const deviceSuffix = device.toLowerCase().replace(/\s+/g, '-');
            const browserKey = getBrowserKey(device);
            const updatedDeps = extra.dependencies.map((dep) => {
                // If dependency references a platform, expand to device-specific name
                const matchingPlatform = platforms.find((p) => p.name === dep);
                if (matchingPlatform) {
                    return `${dep}-${deviceSuffix}`;
                }
                return dep === 'setup' ? `setup-${browserKey}` : dep;
            });
            extraProjectConfigs.push({
                name: `${extra.name}-${deviceSuffix}`,
                testMatch: extra.testMatch,
                use: {
                    ...devices[device],
                    storageState: `playwright/.auth/user-${browserKey}.json`,
                },
                dependencies: updatedDeps,
                testIgnore: [],
            });
        }
    }
    return defineConfig({
        testDir,
        fullyParallel: true,
        forbidOnly: !!process.env.CI,
        retries: process.env.CI ? 3 : 0,
        workers: process.env.CI ? 1 : undefined,
        projects: [...browserSetupProjects, ...platformProjects, ...extraProjectConfigs],
        reporter: [
            [
                'html',
                {
                    outputFolder: process.env.HTML_REPORTER_OUTPUT_DIR || 'playwright-report',
                    open: process.env.CI ? 'never' : 'on-failure',
                },
            ],
            process.env.CI ? ['list', { printSteps: true }] : [customReporterPath],
            ...(process.env.CI ? [['json', { outputFile: '/app/test-results.json' }]] : []),
        ],
        timeout: process.env.CI ? 300000 : 200000,
        use: {
            headless: true,
            trace: process.env.CI ? 'retain-on-failure' : 'off',
            launchOptions: {
                slowMo: process.env.CI ? 0 : 1000,
            },
            ...(process.env.CI && {
                video: 'off',
                screenshot: 'off',
                actionTimeout: 30000,
                navigationTimeout: 30000,
            }),
        },
    });
}

export { AuthFlowBuilder, CustomReporter, MailsacClient, addMemory, archiveFirstMemory, archiveMemoryByContent, buildReportUrl, canChatWithEmbedMentor, checkAdminStatus, clearDateRangeFilter, clickBackHome, clickDownloadAgain, clickManualDownloadLink, closeWithEsc, createAuthSetup, createEnvConfig, createPlaywrightConfig, deleteFirstMemory, deleteMemoryByContent, expectNoAccessibilityViolations, expectNoAccessibilityViolationsOnDialogs, filterByAction, filterByActionAndVerify, filterByActor, filterByActorAndVerify, filterByDateRange, generateBrowserSetupProjects, generateProjectConfig, getAuditLogRowCount, getAvailableActors, getBrowserKey, getMemoryCount, getMentorIdFromUrl, getPaginationInfo, goToFirstPage, goToLastPage, goToNextPage, goToPage, goToPreviousPage, inviteUserTest, isFirefox, isJSON, isMemoryTabVisible, isOnFirstPage, isOnLastPage, logger, loginWithEmailAndPassword, loginWithMicrosoftIdp, navigateToAccountComponent, navigateToAuditLog, navigateToAuditLogAndWaitForData, navigateToDataReports, navigateToReportDownload, openAddMemoryDialog, parseReportUrlParams, reliableClick, reliableFill, retry, safeWaitForURL, selectDateFromCalendar, shouldAddNewRowWhenClickingAddRowButton, shouldAllowEditingCellValuesInCSVEditor, shouldCancelCombiningReports, shouldCloseCSVEditorWhenClickingCloseButton, shouldCloseCSVEditorWithoutSavingWhenClickingCancel, shouldCombineRecommendationReports, shouldDirectlyDownloadChatHistoryReportWithoutCSVEditor, shouldDisableOtherDownloadButtonsWhileGeneratingReport, shouldDisplayCSVInEditableTableFormat, shouldDisplayReportCards, shouldHaveCombinedReportDataTestIds, shouldOpenCSVEditorDialog, shouldOpenCSVEditorForUserMetadataReport, shouldSaveEditedCSVAndTriggerDownload, shouldShowCombiningReportsDialog, shouldVerifyCSVEditorDialogAccessibility, signUpWithEmailAndPassword, switchToMemoryTab, test, toggleMemorySwitch, verifyAuditLogEmptyState, verifyAuditLogEntryStructure, verifyAuditLogGenericError, verifyAuditLogLoading, verifyAuditLogPermissionError, verifyAuditLogTableVisible, verifyCurrentPage, verifyDonePhase, verifyDownloadingPhase, verifyErrorPhase, verifyMemoryExists, verifyMemoryNotExists, verifyMemoryTabMemoriesList, verifyMemoryTabSettings, verifyPreparingPhase, waitForAuditLogDataLoaded, waitForDialogReady, waitForElementStable, waitForPageLoad, waitForPageReady, waitForReportDownload };
//# sourceMappingURL=index.esm.js.map
