/**
 * Unbound port placeholders for deferred `definePorts(...)` keys.
 *
 * Ports declared as deferred boot as marked placeholders. Any method call on
 * a placeholder throws a descriptive error, and `createServer(...)` scans
 * final ports after provider startup so a missing provider fails boot instead
 * of failing on first use.
 */

const UNBOUND_PORT_MARKER = Symbol.for("beignet.unboundPort");

function unboundPortMessage(portName: string): string {
  return (
    `Port "${portName}" is not bound. "${portName}" is declared as deferred ` +
    "in definePorts(...). Register a provider that contributes it " +
    "(server/providers.ts) or bind it in infra/port-wiring.ts."
  );
}

/**
 * Create a marked placeholder for a deferred port.
 *
 * Property access returns a function that throws, so calling any method on an
 * unbound port produces a descriptive error naming the port.
 */
export function createUnboundPort(portName: string): unknown {
  return new Proxy(
    {},
    {
      get(_target, property) {
        if (property === UNBOUND_PORT_MARKER) return true;
        if (property === Symbol.toStringTag) {
          return `UnboundPort(${portName})`;
        }
        // Keep unbound ports safe to `await` and JSON-serialize.
        if (property === "then" || property === "toJSON") return undefined;
        if (typeof property === "symbol") return undefined;
        if (property === "toString") {
          return () => `[unbound port "${portName}"]`;
        }

        return () => {
          throw new Error(unboundPortMessage(portName));
        };
      },
    },
  );
}

/**
 * Check whether a value is an unbound port placeholder created for a deferred
 * `definePorts(...)` key.
 */
export function isUnboundPort(value: unknown): boolean {
  if (value === null || typeof value !== "object") return false;

  try {
    return (
      (value as Record<PropertyKey, unknown>)[UNBOUND_PORT_MARKER] === true
    );
  } catch {
    return false;
  }
}
