import { app, BrowserWindow, dialog } from 'electron';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import { get } from 'node:http';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const bundleRoot = resolve(__dirname, '..');

const nodecgRoot = process.env.NODECG_ROOT
  ? resolve(bundleRoot, process.env.NODECG_ROOT)
  : resolve(bundleRoot, '..', '..');

const startUrl = process.env.ELECTRON_START_URL ?? 'http://localhost:9090/bundles/scoreko-dev/dashboard/example/main.html?standalone=true';
const nodecgPort = Number(process.env.NODECG_PORT ?? 9090);
const nodeBinary = process.env.NODE_BINARY ?? 'node';
const useElectronNodeForNodeCG = process.env.NODECG_USE_ELECTRON_NODE === '1';

/** @type {import('node:child_process').ChildProcess | undefined} */
let nodecgProcess;

function waitForServer(url, timeoutMs = 30_000) {
  const started = Date.now();

  return new Promise((resolvePromise, rejectPromise) => {
    const attempt = () => {
      const request = get(url, (response) => {
        response.resume();

        if (response.statusCode && response.statusCode >= 200 && response.statusCode < 500) {
          resolvePromise();
          return;
        }

        if (Date.now() - started > timeoutMs) {
          rejectPromise(new Error('NodeCG did not respond in time.'));
          return;
        }

        setTimeout(attempt, 500);
      });

      request.on('error', () => {
        if (Date.now() - started > timeoutMs) {
          rejectPromise(new Error('Could not connect to NodeCG.'));
          return;
        }

        setTimeout(attempt, 500);
      });
    };

    attempt();
  });
}

function startNodeCG() {
  const runtimeBinary = useElectronNodeForNodeCG ? process.execPath : nodeBinary;

  nodecgProcess = spawn(runtimeBinary, ['index.js'], {
    cwd: nodecgRoot,
    stdio: 'inherit',
    env: {
      ...process.env,
      NODE_ENV: process.env.NODE_ENV ?? 'production',
      PORT: String(nodecgPort)
    }
  });

  nodecgProcess.on('exit', (code) => {
    if (!app.isQuiting) {
      dialog.showErrorBox(
        'NodeCG exited',
        `The NodeCG process ended unexpectedly with code ${code ?? 'unknown'}.`
      );
      app.quit();
    }
  });

  nodecgProcess.on('error', (error) => {
    dialog.showErrorBox(
      'Could not start NodeCG',
      `Could not run \"${runtimeBinary}\". ${useElectronNodeForNodeCG ? 'Disable NODECG_USE_ELECTRON_NODE or check your Electron setup.' : 'Set NODE_BINARY to your Node.js path.'}\n\nDetails: ${error.message}`
    );
    app.quit();
  });
}

async function createWindow() {
  const win = new BrowserWindow({
    width: 1920,
    height: 1080,
    autoHideMenuBar: true,
    backgroundColor: '#000000',
    webPreferences: {
      contextIsolation: true,
      sandbox: true
    }
  });

  await win.loadURL(startUrl);
}

app.on('before-quit', () => {
  app.isQuiting = true;

  if (nodecgProcess && !nodecgProcess.killed) {
    nodecgProcess.kill('SIGTERM');
  }
});

app.whenReady().then(async () => {
  startNodeCG();

  try {
    await waitForServer(`http://127.0.0.1:${nodecgPort}`);
    await createWindow();
  } catch (error) {
    dialog.showErrorBox('Could not start', error instanceof Error ? error.message : String(error));
    app.quit();
  }
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});
