/**
 * desktop/src/port.ts — pick a bindable TCP port for the embedded server.
 *
 * The backend derives allowedOrigins from the concrete port (config.ts M1), so
 * the desktop shell must resolve a DEFINITE port BEFORE loadConfig — never hand
 * loadConfig a PORT=0, or http://127.0.0.1:<port> would fall off the Origin
 * whitelist. We probe the preferred port on the loopback interface; if it is
 * taken (EADDRINUSE) we ask the OS for a free one (listen on 0), read it, and
 * close — so the returned port was confirmed bindable a moment ago.
 */

import net from 'node:net'

/** Loopback host we probe against — the embedded server always binds here in private mode. */
const LOOPBACK_HOST = '127.0.0.1'
/** listen(OS_ASSIGN_PORT) → the kernel picks a free ephemeral port for us. */
const OS_ASSIGN_PORT = 0

/**
 * Return `preferred` if it can be bound on 127.0.0.1; otherwise an OS-assigned
 * free port. A bind failure that is EADDRINUSE falls back to an OS-assigned
 * port; any other error (e.g. EACCES on a privileged port) is rethrown.
 */
export async function pickFreePort(preferred: number): Promise<number> {
  try {
    return await tryBind(preferred)
  } catch (err: unknown) {
    if (isAddrInUse(err)) return tryBind(OS_ASSIGN_PORT)
    throw err
  }
}

/** Bind a throwaway server on `port` (0 → OS-assigned), read the actual port, close, resolve it. */
function tryBind(port: number): Promise<number> {
  return new Promise<number>((resolve, reject) => {
    const server = net.createServer()
    server.on('error', (err) => {
      server.close()
      reject(err)
    })
    server.listen(port, LOOPBACK_HOST, () => {
      const address = server.address()
      const bound = address !== null && typeof address === 'object' ? address.port : null
      server.close(() => {
        if (bound === null) {
          reject(new Error('pickFreePort: server.address() did not return an AddressInfo'))
          return
        }
        resolve(bound)
      })
    })
  })
}

/** True iff `err` is a Node errno error whose code is EADDRINUSE. */
function isAddrInUse(err: unknown): boolean {
  return (
    typeof err === 'object' &&
    err !== null &&
    'code' in err &&
    err.code === 'EADDRINUSE'
  )
}
