'use strict';

const common = require('../common');
const Countdown = require('../common/countdown');
const {
  parentPort,
  postMessageToThread,
  threadId,
  workerData,
  Worker,
} = require('node:worker_threads');
const assert = require('node:assert');
const { once } = require('node:events');

// Spawn threads on three levels: 1 main thread, two children, four grand childrens. 7 threads total, max id = 6
const MAX_LEVEL = 2;
const MAX_THREAD = 6;

// This is to allow the test to run in --worker mode
const mainThread = workerData?.mainThread ?? threadId;
const level = workerData?.level ?? 0;

const channel = new BroadcastChannel('nodejs:test-worker-connection');
let completed;

if (level === 0) {
  completed = new Countdown(MAX_THREAD + 1, () => {
    channel.postMessage('exit');
    channel.close();
  });
}

async function createChildren() {
  const worker = new Worker(__filename, { workerData: { mainThread, level: level + 1 } });
  await once(worker, 'message');
}

async function ping() {
  let target;
  do {
    target = mainThread + Math.floor(Math.random() * MAX_THREAD);
  } while (target === threadId);

  const { port1, port2 } = new MessageChannel();
  await postMessageToThread(target, { level, port: port2 }, [port2]);

  port1.on('message', common.mustCall(function(message) {
    assert.deepStrictEqual(message, { message: 'pong', source: target, destination: threadId });
    port1.close();

    if (level === 0) {
      completed.dec();
    } else {
      channel.postMessage('end');
    }
  }));

  port1.postMessage({ message: 'ping', source: threadId, destination: target });
}

// Do not use mustCall here as the thread might not receive any connection request
process.on('workerMessage', common.mustCallAtLeast(({ port, level }, source) => {
  // Let's verify the source hierarchy
  // Given we do depth first, the level is 1 for thread 1 and 4, 2 for other threads
  if (source !== mainThread) {
    const currentThread = source - mainThread;
    assert.strictEqual(level, (currentThread === 1 || currentThread === 4) ? 1 : 2);
  } else {
    assert.strictEqual(level, 0);
  }

  // Verify communication
  port.on('message', common.mustCall(function(message) {
    assert.deepStrictEqual(message, { message: 'ping', source, destination: threadId });
    port.postMessage({ message: 'pong', source: threadId, destination: source });
    port.close();
  }));
}, 0));

async function test() {
  if (level < MAX_LEVEL) {
    await createChildren();
    await createChildren();
  }

  channel.onmessage = function(message) {
    switch (message.data) {
      case 'start':
        ping();
        break;
      case 'end':
        if (level === 0) {
          completed.dec();
        }
        break;
      case 'exit':
        channel.close();
        break;
    }
  };

  if (level > 0) {
    const currentThread = threadId - mainThread;
    assert.strictEqual(level, (currentThread === 1 || currentThread === 4) ? 1 : 2);
    parentPort.postMessage({ type: 'ready', threadId });
  } else {
    channel.postMessage('start');
    ping();
  }
}

test();
