/*
 * Licensed to Apache Software Foundation (ASF) under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Apache Software Foundation (ASF) licenses this file to you under
 * the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

// Dev-mode wrapper for the Canopy BFF.
//
// Watches `canopy/web/dist/` and respawns the BFF whenever the production
// web bundle is rebuilt. This is the missing piece: the BFF caches
// in-memory references to the bundle HTML + asset paths at startup, so
// rebuilding `web/dist/` without restarting the BFF served stale references.
//
// Usage:
//   node scripts/dev-bff.mjs
//
// Press Ctrl+C once to stop everything.

import { spawn } from 'node:child_process';
import { watch, existsSync } from 'node:fs';
import { resolve, dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '..', '..');
const DIST_DIR = resolve(REPO_ROOT, 'canopy', 'web', 'dist');
const SERVER_CWD = resolve(REPO_ROOT, 'canopy', 'server');

let child = null;
let restarting = false;
let pendingRestart = false;
let userStopping = false;

function startBff() {
  console.log(`[dev-bff] starting BFF (port 4000, watching ${DIST_DIR})`);
  child = spawn('node', ['--loader', 'ts-node/esm', 'src/index.ts'], {
    cwd: SERVER_CWD,
    env: {
      ...process.env,
      SESSION_SECRET: process.env.SESSION_SECRET ?? 'canopy-e2e-test-secret-32chars!!',
      CANOPY_DEV_NOAUTH: process.env.CANOPY_DEV_NOAUTH ?? 'true',
      BANYANDB_TARGET: process.env.BANYANDB_TARGET ?? 'http://127.0.0.1:17913',
      PORT: '4000',
      LOG_LEVEL: process.env.LOG_LEVEL ?? 'warn',
    },
    stdio: 'inherit',
  });
  child.on('exit', (code, signal) => {
    if (userStopping) {
      console.log(`[dev-bff] BFF exited (${signal}) — user-requested stop, exiting wrapper`);
      process.exit(0);
    }
    if (signal === 'SIGINT' || signal === 'SIGTERM') {
      // Killed by the watcher-triggered restart path; the once('exit', ...)
      // handler in restartBff is responsible for respawning. Do nothing here.
      return;
    }
    if (!restarting) {
      console.log(`[dev-bff] BFF exited unexpectedly (code=${code}); respawning in 1s`);
      setTimeout(() => startBff(), 1000);
    }
  });
}

function restartBff(reason) {
  if (restarting) { pendingRestart = true; return; }
  restarting = true;
  console.log(`[dev-bff] ${reason} — restarting BFF`);
  if (child) {
    child.once('exit', () => {
      restarting = false;
      startBff();
      if (pendingRestart) { pendingRestart = false; /* subsequent change already queued by the watcher */ }
    });
    child.kill('SIGTERM');
  } else {
    restarting = false;
    startBff();
  }
}

if (!existsSync(DIST_DIR)) {
  console.error(`[dev-bff] ${DIST_DIR} does not exist — run \`npm run -w web build\` first`);
  process.exit(1);
}

startBff();

// Watch dist for rebuilds. Use recursive + a debounce so multi-file writes
// (Vite writes index.html + assets/*.js + assets/*.css) trigger one restart.
let timer = null;
function notifyChange(filename) {
  if (!filename) return;
  if (timer) clearTimeout(timer);
  timer = setTimeout(() => restartBff(`dist changed: ${filename}`), 200);
}

try {
  watch(DIST_DIR, { recursive: true }, (_event, filename) => notifyChange(filename));
  console.log('[dev-bff] watching dist/ for changes');
} catch (e) {
  console.error(`[dev-bff] failed to watch ${DIST_DIR}: ${e.message}`);
  console.error('[dev-bff] (recursive watch is required; Node 20+ on Linux is supported)');
}

process.on('SIGINT', () => {
  userStopping = true;
  console.log('[dev-bff] SIGINT — stopping BFF and exiting wrapper');
  if (child) child.kill('SIGTERM');
  // The child.on('exit') handler will call process.exit(0) once userStopping is true.
  setTimeout(() => process.exit(0), 2000).unref();
});
process.on('SIGTERM', () => {
  userStopping = true;
  if (child) child.kill('SIGTERM');
  setTimeout(() => process.exit(0), 2000).unref();
});
