function build_event_handler(node, metadata, context) {
	if (node === null) {
		// bubble event
		return b.function(
			null,
			[b.id('$$arg')],
			b.block([b.stmt(b.call('$.bubble_event.call', b.this, b.id('$$props'), b.id('$$arg')))])
		);
	}

	let handler = /** @type {Expression} */ (context.visit(node));

	// inline handler
	if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
		return handler;
	}

	// function declared in the script
	if (handler.type === 'Identifier') {
		const binding = context.state.scope.get(handler.name);

		if (binding?.is_function()) {
			return handler;
		}

		// local variable can be assigned directly
		// except in dev mode where when need $.apply()
		// in order to handle warnings.
		if (!dev && binding?.declaration_kind !== 'import') {
			return handler;
		}
	}

	if (metadata.has_call) {
		// memoize where necessary
		const id = b.id(context.state.scope.generate('event_handler'));

		context.state.init.push(b.var(id, b.call('$.derived', b.thunk(handler))));
		handler = b.call('$.get', id);
	}

	// wrap the handler in a function, so the expression is re-evaluated for each event
	let call = b.call(b.member(handler, 'apply', false, true), b.this, b.id('$$args'));

	if (dev) {
		const loc = locator(/** @type {number} */ (node.start));

		const remove_parens =
			node.type === 'CallExpression' &&
			node.arguments.length === 0 &&
			node.callee.type === 'Identifier';

		call = b.call(
			'$.apply',
			b.thunk(handler),
			b.this,
			b.id('$$args'),
			b.id(context.state.analysis.name),
			loc && b.array([b.literal(loc.line), b.literal(loc.column)]),
			has_side_effects(node) && b.true,
			remove_parens && b.true
		);
	}

	return b.function(null, [b.rest(b.id('$$args'))], b.block([b.stmt(call)]));
}
Creates an event handler
@param {Expression | null} node
@param {ExpressionMetadata} metadata
@param {ComponentContext} context
@returns {Expression}