const { installForTests, removeRecursiveSync } = require('./esbuild')
const child_process = require('child_process')
const path = require('path')
const fs = require('fs')
const assert = require('assert')
const esbuild = installForTests()

// Create a fresh test directory
const rootTestDir = path.join(__dirname, '.register-test')
removeRecursiveSync(rootTestDir)
fs.mkdirSync(rootTestDir)

const entry = path.join(rootTestDir, 'entry.ts')
fs.writeFileSync(entry, `
  console.log('in entry.ts' as string)
  require('./other.ts')
`)

const other = path.join(rootTestDir, 'other.ts')
fs.writeFileSync(other, `
  console.log('in other.ts' as string)
`)

const register = path.join(rootTestDir, 'register.js')
fs.writeFileSync(register, `
  const esbuild = require(${JSON.stringify(esbuild.ESBUILD_PACKAGE_PATH)});
  const fs = require('fs');
  require.extensions['.ts'] = (mod, filename) => {
    const ts = fs.readFileSync(filename, 'utf8');
    const { code } = esbuild.transformSync(ts, { loader: 'ts' });
    mod._compile(code, filename);
  };
`)

let tests = {
  async fromMainThread() {
    let result = await new Promise((resolve, reject) => child_process.execFile('node', ['-r', register, entry], (err, stdout) => {
      if (err) reject(err)
      else resolve(stdout)
    }))
    assert.strictEqual(result, `in entry.ts\nin other.ts\n`)
  },

  async fromChildThread({ testDir }) {
    const startThread = path.join(testDir, 'startThread.js')
    fs.writeFileSync(startThread, `
      const worker_threads = require('worker_threads')
      if (worker_threads.isMainThread) {
        console.log('in startThread.js')
        const worker = new worker_threads.Worker(__filename)
        worker.postMessage(null)
        worker.on('message', logs => {
          for (const log of logs) console.log(log)
          worker.terminate()
        })
      } else {
        worker_threads.parentPort.on('message', () => {
          console.log('in worker')
          let logs = []
          console.log = x => logs.push(x)
          require('../entry.ts')
          worker_threads.parentPort.postMessage(logs)
        })
      }
    `)

    let result = await new Promise((resolve, reject) => child_process.execFile('node', ['-r', register, startThread], (err, stdout) => {
      if (err) reject(err)
      else resolve(stdout)
    }))
    assert.strictEqual(result, `in startThread.js\nin worker\nin entry.ts\nin other.ts\n`)
  },
}

async function main() {
  // Time out these tests after 5 minutes. This exists to help debug test hangs in CI.
  let minutes = 5
  let timeout = setTimeout(() => {
    console.error(`❌ register tests timed out after ${minutes} minutes, exiting...`)
    process.exit(1)
  }, minutes * 60 * 1000)

  const runTest = async ([name, fn]) => {
    let testDir = path.join(rootTestDir, name)
    try {
      fs.mkdirSync(testDir)
      await fn({ esbuild, testDir })
      removeRecursiveSync(testDir)
      return true
    } catch (e) {
      console.error(`❌ ${name}: ${e && e.message || e}`)
      return false
    }
  }

  // Run all tests in serial
  let allTestsPassed = true
  for (let test of Object.entries(tests)) {
    if (!await runTest(test)) {
      allTestsPassed = false
    }
  }

  if (!allTestsPassed) {
    console.error(`❌ register tests failed`)
    process.exit(1)
  } else {
    console.log(`✅ register tests passed`)
    removeRecursiveSync(rootTestDir)
  }

  clearTimeout(timeout);
}

main().catch(e => setTimeout(() => { throw e }))
