/* eslint:disable: no-console */

import * as path from 'path';
import * as fs from 'fs';
import { EventEmitter } from 'node:events';
import inquirer from 'inquirer';
import puppeteer from 'puppeteer';
import { fileURLToPath } from 'url';

import { startServer, getServerURL } from './utils.js';

// Turn on devtools for debugging:
const devtools = false;
const defaultURL = 'https://webglsamples.org/';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const emitter = new EventEmitter();
const code = fs.readFileSync(path.join(__dirname, '../dist/rrweb.js'), 'utf8');
const pluginCode = fs.readFileSync(
  path.join(__dirname, '../dist/plugins/canvas-webrtc-record.js'),
  'utf8',
);

async function injectRecording(frame) {
  await frame.evaluate(
    (rrwebCode, pluginCode) => {
      const win = window;
      if (win.__IS_RECORDING__) return;
      win.__IS_RECORDING__ = true;

      (async () => {
        function loadScript(code) {
          const s = document.createElement('script');
          s.type = 'text/javascript';
          s.innerHTML = code;
          if (document.head) {
            document.head.append(s);
          } else {
            requestAnimationFrame(() => {
              document.head.append(s);
            });
          }
        }
        loadScript(rrwebCode);
        loadScript(pluginCode);

        win.events = [];
        window.record = win.rrweb.record;
        window.plugin = new rrwebCanvasWebRTCRecord.RRWebPluginCanvasWebRTCRecord(
          {
            signalSendCallback: (msg) => {
              // [record#callback] provides canvas id, stream, and webrtc sdpOffer signal & connect message
              _signal(msg);
            },
          },
        );

        window.record({
          emit: (event) => {
            win.events.push(event);
            win._captureEvent(event);
          },
          plugins: [window.plugin.initPlugin()],
          recordCanvas: false,
          recordCrossOriginIframes: true,
          collectFonts: true,
          inlineImages: true,
        });
      })();
    },
    code,
    pluginCode,
  );
}

async function startReplay(page, serverURL, recordedPage) {
  await recordedPage.exposeFunction('_signal', async (signal) => {
    await page.evaluate((signal) => {
      // [replay#signalReceive] setups up peer and starts creating counter offer
      window.plugin.signalReceive(signal);
    }, signal);
  });
  await page.exposeFunction('_signal', async (signal) => {
    await recordedPage.evaluate((signal) => {
      // [record#signalReceive]  setups up webrtc connection
      window.plugin.signalReceive(signal);
    }, signal);
  });
  await page.exposeFunction('_canvas', async (id) => {
    await recordedPage.evaluate((id) => {
      // [record#setupStream] sets up the canvas stream for a given id.
      const stream = window.plugin.setupStream(id);
      console.log('stream for', id, '=>', stream);
    }, id);
  });

  await page.addScriptTag({ url: `${serverURL}/rrweb.js` });
  await page.addScriptTag({
    url: `${serverURL}/plugins/canvas-webrtc-replay.js`,
  });

  return page.evaluate(() => {
    window.plugin = new rrwebCanvasWebRTCReplay.RRWebPluginCanvasWebRTCReplay({
      canvasFoundCallback(canvas, context) {
        console.log('canvas', canvas, context);
        // [replay#onBuild] gets id of canvas element and sends to recorded page
        _canvas(context.id);
      },
      signalSendCallback(data) {
        _signal(JSON.stringify(data));
      },
    });

    window.replayer = new rrweb.Replayer([], {
      UNSAFE_replayCanvas: true,
      liveMode: true,
      plugins: [window.plugin.initPlugin()],
    });
    window.replayer.startLive();
    const style = new CSSStyleSheet();
    style.replaceSync('body {margin: 0;} iframe {border: none;}');
    document.adoptedStyleSheets = [style];
  });
}

async function resizeWindow(page, top, left, width, height) {
  const session = await page.target().createCDPSession();
  await page.setViewport({ height, width });
  const { windowId } = await session.send('Browser.getWindowForTarget');
  await session.send('Browser.setWindowBounds', {
    bounds: { top, left, height, width },
    windowId,
  });
}

void (async () => {
  let server;
  let serverURL;

  await start();

  async function start() {
    server = await startServer();
    serverURL = getServerURL(server);

    const { url } = await inquirer.prompt([
      {
        type: 'input',
        name: 'url',
        message: `Enter the url you want to record, e.g ${defaultURL}: `,
      },
    ]);

    await record(url);

    const { shouldRecordAnother } = await inquirer.prompt([
      {
        type: 'confirm',
        name: 'shouldRecordAnother',
        message: 'Record another one?',
      },
    ]);

    emitter.emit('done');

    if (shouldRecordAnother) {
      await start();
    } else {
      process.exit();
    }
  }

  async function record(url) {
    if (url === '') url = defaultURL;

    const replayingBrowser = await puppeteer.launch({
      headless: false,
      devtools,
      defaultViewport: {
        width: 1600,
        height: 900,
      },
      args: [
        '--start-maximized',
        '--ignore-certificate-errors',
        '--no-sandbox',
      ],
    });

    const replayerPage = (await replayingBrowser.pages())[0];
    await replayerPage.goto('about:blank');

    await replayerPage.addStyleTag({
      path: path.resolve(__dirname, '../dist/rrweb.css'),
    });

    const recordingBrowser = await puppeteer.launch({
      headless: false,
      devtools,
      defaultViewport: {
        width: 1600,
        height: 900,
      },
      args: [
        '--start-maximized',
        '--ignore-certificate-errors',
        '--no-sandbox',
      ],
    });

    const recordedPage = (await recordingBrowser.pages())[0];
    if (!recordedPage) {
      throw new Error('No recorded page found');
    }
    // disables content security policy which enables us to insert rrweb as a script tag
    await recordedPage.setBypassCSP(true);

    replayerPage.on('console', (msg) =>
      console.log('REPLAY PAGE LOG:', msg.text()),
    );
    recordedPage.on('console', (msg) =>
      console.log('RECORD PAGE LOG:', msg.text()),
    );
    await startReplay(replayerPage, serverURL, recordedPage);

    await Promise.all([
      resizeWindow(recordedPage, 0, 0, 800, 800),
      resizeWindow(replayerPage, 0, 800, 800, 800),
    ]);

    await recordedPage.exposeFunction('_captureEvent', (event) => {
      replayerPage.evaluate((event) => {
        window.replayer.addEvent(event);
      }, event);
    });

    recordedPage.on('framenavigated', async (frame) => {
      console.log('framenavigated');
      await injectRecording(frame, serverURL);
    });

    await recordedPage.goto(url, {
      waitUntil: 'domcontentloaded',
      timeout: 300000,
    });

    if (!replayerPage) throw new Error('No replayer page found');

    emitter.once('done', async () => {
      const pages = [
        ...(await recordingBrowser.pages()),
        ...(await replayingBrowser.pages()),
      ];
      await server.close();
      await Promise.all(pages.map((page) => page.close()));
      await recordingBrowser.close();
      await replayingBrowser.close();
    });
  }

  process
    .on('uncaughtException', (error) => {
      console.error(error);
    })
    .on('unhandledRejection', (error) => {
      console.error(error);
    });
})();
