So a typical chain ends up being legacy app -> ALSA -> PulseAudio -> ALSA -> kernel, because ALSA both frontends PA for legacy ALSA-only apps to use it, and also backends PA to talk to the real hardware.
But ALSA isn't the only backend for PA. For example, Bluetooth audio output will go via BlueZ and into kernel socket APIs instead, since Bluetooth audio is a network protocol, not something handled in the kernel. And then there's FFADO, a userspace driver for FireWire audio devices. And you can put JACK on top of that. And then you can put PulseAudio on top of JACK. Or you can out JACK on top of PulseAudio, but that'd be silly. And PA can go over the network, both from app to a remote PA daemon, and also from PA daemon to PA daemon. And there's also netJACK if you want to put that on a network.
And now PipeWire is replacing JACK and PulseAudio and impersonates their APIs and also ALSA and lets everything talk to everything else.
Basically it's complicated, but also very flexible. In the end though, there is usually one "default" setup that you get if you don't do anything on a typical distro, and that's, these days, apps -> [ALSA ->] PulseAudio -> {ALSA -> kernel, BlueZ -> kernel} for most typical use cases (native PA apps and ALSA apps, local audio and bluetooth backends).