static int feature_automixmonitor(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
	static const char *mixmonitor_spy_type = "MixMonitor";
	const char *stop_message;
	const char *start_message;
	struct ast_bridge_features_automixmonitor *options = hook_pvt;
	enum ast_bridge_features_monitor start_stop = options ? options->start_stop : AUTO_MONITOR_TOGGLE;
	int is_monitoring;

	RAII_VAR(struct ast_channel *, peer_chan, NULL, ast_channel_cleanup);
	RAII_VAR(struct ast_features_general_config *, features_cfg, NULL, ao2_cleanup);

	features_cfg = ast_get_chan_features_general_config(bridge_channel->chan);
	ast_bridge_channel_lock_bridge(bridge_channel);
	peer_chan = ast_bridge_peer_nolock(bridge_channel->bridge, bridge_channel->chan);
	ast_bridge_unlock(bridge_channel->bridge);

	if (!peer_chan) {
		ast_verb(3, "Cannot do AutoMixMonitor for %s - cannot determine peer in bridge.\n",
			ast_channel_name(bridge_channel->chan));
		if (features_cfg && !ast_strlen_zero(features_cfg->recordingfailsound)) {
			ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->recordingfailsound, NULL);
		}
		return 0;
	}

	ast_channel_lock(bridge_channel->chan);
	start_message = pbx_builtin_getvar_helper(bridge_channel->chan,
		"TOUCH_MIXMONITOR_MESSAGE_START");
	start_message = ast_strdupa(S_OR(start_message, ""));
	stop_message = pbx_builtin_getvar_helper(bridge_channel->chan,
		"TOUCH_MIXMONITOR_MESSAGE_STOP");
	stop_message = ast_strdupa(S_OR(stop_message, ""));
	ast_channel_unlock(bridge_channel->chan);

	is_monitoring =
		0 < ast_channel_audiohook_count_by_source(peer_chan, mixmonitor_spy_type, AST_AUDIOHOOK_TYPE_SPY);
	switch (start_stop) {
	case AUTO_MONITOR_TOGGLE:
		if (is_monitoring) {
			stop_automixmonitor(bridge_channel, peer_chan, features_cfg, stop_message);
		} else {
			start_automixmonitor(bridge_channel, peer_chan, features_cfg, start_message);
		}
		return 0;
	case AUTO_MONITOR_START:
		if (!is_monitoring) {
			start_automixmonitor(bridge_channel, peer_chan, features_cfg, start_message);
			return 0;
		}
		ast_verb(3, "AutoMixMonitor already recording call.\n");
		break;
	case AUTO_MONITOR_STOP:
		if (is_monitoring) {
			stop_automixmonitor(bridge_channel, peer_chan, features_cfg, stop_message);
			return 0;
		}
		ast_verb(3, "AutoMixMonitor already not recording call.\n");
		break;
	}

	/*
	 * Fake start/stop to invoker so will think it did something but
	 * was already in that mode.
	 */
	if (features_cfg && !ast_strlen_zero(features_cfg->courtesytone)) {
		ast_bridge_channel_queue_playfile(bridge_channel, NULL, features_cfg->courtesytone, NULL);
	}
	if (is_monitoring) {
		if (!ast_strlen_zero(start_message)) {
			ast_bridge_channel_queue_playfile(bridge_channel, NULL, start_message, NULL);
		}
	} else {
		if (!ast_strlen_zero(stop_message)) {
			ast_bridge_channel_queue_playfile(bridge_channel, NULL, stop_message, NULL);
		}
	}
	return 0;
}
/*!
 * \internal
 * \brief Determine if an extension is a parking extension
 */
static int parking_is_exten_park(const char *context, const char *exten)
{
	struct ast_exten *exten_obj;
	struct pbx_find_info info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
	const char *app_at_exten;

	ast_debug(4, "Checking if %s@%s is a parking exten\n", exten, context);
	exten_obj = pbx_find_extension(NULL, NULL, &info, context, exten, 1, NULL, NULL, E_MATCH);
	if (!exten_obj) {
		return 0;
	}

	app_at_exten = ast_get_extension_app(exten_obj);
	if (!app_at_exten || strcasecmp(PARK_APPLICATION, app_at_exten)) {
		return 0;
	}

	return 1;
}

/*!
 * \internal
 * \since 12.0.0
 * \brief Perform a blind transfer to a parking lot
 *
 * In general, most parking features should work to call this function. This will safely
 * park either a channel in the bridge with \ref bridge_channel or will park the entire
 * bridge if more than one channel is in the bridge. It will create the correct data to
 * pass to the \ref AstBridging Bridging API to safely park the channel.
 *
 * \param bridge_channel The bridge_channel representing the channel performing the park
 * \param context The context to blind transfer to
 * \param exten The extension to blind transfer to
 * \param parked_channel_cb Optional callback executed prior to sending the parked channel into the bridge
 * \param parked_channel_data Data for the parked_channel_cb
 *
 * \retval 0 on success
 * \retval non-zero on error
 */
static int parking_blind_transfer_park(struct ast_bridge_channel *bridge_channel,
		const char *context, const char *exten, transfer_channel_cb parked_channel_cb,
		struct transfer_channel_data *parked_channel_data)
{
	RAII_VAR(struct ast_bridge_channel *, other, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, other_chan, NULL, ast_channel_cleanup);

	struct ast_exten *e;
	struct pbx_find_info find_info = { .stacklen = 0 };
	int peer_count;

	if (ast_strlen_zero(context) || ast_strlen_zero(exten)) {
		return -1;
	}

	if (!bridge_channel->in_bridge) {
		return -1;
	}

	if (!parking_is_exten_park(context, exten)) {
		return -1;
	}

	ast_bridge_channel_lock_bridge(bridge_channel);
	peer_count = bridge_channel->bridge->num_channels;
	if (peer_count == 2) {
		other = ast_bridge_channel_peer(bridge_channel);
		ao2_ref(other, +1);
		other_chan = other->chan;
		ast_channel_ref(other_chan);
	}
	ast_bridge_unlock(bridge_channel->bridge);

	if (peer_count < 2) {
		/* There is nothing to do if there is no one to park. */
		return -1;
	}

	/* With a multiparty bridge, we need to do a regular blind transfer. We link the
	 * existing bridge to the parking lot with a Local channel rather than
	 * transferring others. */
	if (peer_count > 2) {
		struct ast_channel *transfer_chan = NULL;

		transfer_chan = park_local_transfer(bridge_channel->chan, context, exten, parked_channel_data);
		if (!transfer_chan) {
			return -1;
		}
		ast_channel_ref(transfer_chan);

		if (parked_channel_cb) {
			parked_channel_cb(transfer_chan, parked_channel_data, AST_BRIDGE_TRANSFER_MULTI_PARTY);
		}

		if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL,
			AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
			ast_hangup(transfer_chan);
			ast_channel_unref(transfer_chan);
			return -1;
		}

		ast_channel_unref(transfer_chan);

		return 0;
	}

	/* Subscribe to park messages with the other channel entering */
	if (create_parked_subscription_full(bridge_channel->chan, ast_channel_uniqueid(other->chan), 1, parked_channel_data)) {
		return -1;
	}

	if (parked_channel_cb) {
		parked_channel_cb(other_chan, parked_channel_data, AST_BRIDGE_TRANSFER_SINGLE_PARTY);
	}

	e = pbx_find_extension(NULL, NULL, &find_info, context, exten, 1, NULL, NULL, E_MATCH);

	/* Write the park frame with the intended recipient and other data out to the bridge. */
	ast_bridge_channel_write_park(bridge_channel,
		ast_channel_uniqueid(other_chan),
		ast_channel_uniqueid(bridge_channel->chan),
		e ? ast_get_extension_app_data(e) : NULL);

	return 0;
}

/*!
 * \internal
 * \since 12.0.0
 * \brief Perform a direct park on a channel in a bridge
 *
 * \note This will be called from within the \ref AstBridging Bridging API
 *
 * \param bridge_channel The bridge_channel representing the channel to be parked
 * \param uuid_parkee The UUID of the channel being parked
 * \param uuid_parker The UUID of the channel performing the park
 * \param app_data Application parseable data to pass to the parking application
 */
static int parking_park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
{
	RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
	RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
	RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);

	if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
		/* We aren't the parkee, so ignore this action. */
		return -1;
	}

	parker = ast_channel_get_by_name(uuid_parker);

	if (!parker) {
		ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	if (!(parking_bridge = park_application_setup(bridge_channel->chan, parker, app_data, NULL))) {
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	ast_bridge_set_transfer_variables(bridge_channel->chan, ast_channel_name(parker), 0);

	/* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
	ao2_lock(bridge_channel);

	original_bridge = bridge_channel->bridge;
	if (!original_bridge) {
		ao2_unlock(bridge_channel);
		publish_parked_call_failure(bridge_channel->chan);
		return -1;
	}

	ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */

	ao2_unlock(bridge_channel);

	if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
		ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
			ast_channel_name(bridge_channel->chan));
		return -1;
	}

	return 0;
}