/**
 * Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
 * (after an optional series of commands to the puppeteer Page)
 */


import puppeteer, {Page} from "puppeteer";
import {fileURLToPath, URL} from "node:url";
import {isInDebugMode} from "./debug-mode.ts";


export type PageTestCallback = (page: Page)=>Promise<void>;

export interface TestOptions {
    path: string
    cb: PageTestCallback
    replaceHost: boolean
    replaceHostWith?: string
}
const defaultOptions: Partial<TestOptions> = {
    path: 'index.html',
    cb: async (page: Page)=>{
        await page.waitForNetworkIdle({});
    },
    replaceHost: true,
    replaceHostWith: `http://localhost`,
}
export interface TestOutput{
    html: string,
    console: string[],
    errors: string[],
    responses: string[],
    requestsFailed: string[],
}

/**
 * Opens a page in a puppeteer browser and return the resulting HTML and logmessages produced.
 * Optionally a callback can be provided to simulate user interactions on the page before returning the HTML
 * When DEBUG mode is detected, puppeteer headless mode will be disabled allowing you to inspect the page if you set a breakpoint
 *
 * @param opts
 * @param hostUrl
 */
export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: string){
    const options : TestOptions = (<TestOptions>{
        ...defaultOptions,
        ...opts,
    });
    const {
        path,
        cb,
        replaceHost,
        replaceHostWith,
    } = options;

    const browser = await puppeteer.launch({
        headless: isInDebugMode()? false : 'new',
    });
    const page = await browser.newPage();

    let output : TestOutput = {
        html: '',
        console: [],
        errors: [],
        responses: [],
        requestsFailed: []
    };

    let errored = false;

    try {
        // Track requests, errors and console
        page.on('console', message => {
            let [type, text] = [message.type(), message.text()];
            if (replaceHost) {
                text = text.replaceAll(hostUrl, replaceHostWith!);
            }
            output.console?.push(`[${type}] ${text}`);
        }).on('pageerror', ({message}) => {
            let text = message;
            if (replaceHost) {
                text = text.replaceAll(hostUrl, replaceHostWith!);
            }
            output.errors?.push(text);
        }).on('response', response => {
            let [status, url] = [response.status(), response.url()]
            if (replaceHost) {
                url = url.replaceAll(hostUrl, replaceHostWith!);
            }
            output.responses?.push(`${status} ${url}`);
        }).on('requestfailed', request => {
            let [failure, url] = [request.failure()?.errorText, request.url()];
            if (replaceHost) {
                failure = failure?.replaceAll(hostUrl, replaceHostWith!);
                url = url.replaceAll(hostUrl, replaceHostWith!);
            }
            output.requestsFailed?.push(`${failure} ${url}`);
        });

        const url = new URL(path??'', hostUrl);
        await page.goto(url.href);

        if (!cb) {
            await page.waitForNetworkIdle({});
        } else {
            await cb(page);
        }
        const htmlHandle = await page.$('html');
        const html = await page.evaluate(html => html?.outerHTML ?? html?.innerHTML, htmlHandle);

        // Add the final html
        output.html = html || '';

        return output;
    }catch(err){
        errored = true;
        throw err;
    }finally{
        if(isInDebugMode() && !errored){
            console.log(`DEBUG MODE ENABLED, Close the puppeteer browsertab to continue!\n${import.meta.url}:144`);
            await new Promise((resolve)=>{
                page.on('close', ()=>{
                    console.log("Page closed");
                    resolve(null);
                })
            });
        }else{
            await page.close();
        }
        await browser.close();
    }
}

