Struct or Changeset: Which to maniuplate?

I have gotten myself rather confused working with LiveView and I need some clarity.

I have a complex form with lots of interaction between components/fields. I am using embedded schemas. When I handle the phx-change event I take the params and produce a changeset. If valid I then start doing the heavy lifting – for instance a number of fields are calculated based on other fields.

So when I wrote the function to do these calculations, it takes the struct as an argument and returns a new “updated” struct. In fact, this function is in the schema module, e.g. def calculate(%__MODULE__{} = datastruct) ....

But for LiveView what I have is a changeset. I start out with a changeset, to ensure inputs are valid, and I have to end up with changeset, assigning it to the socket via to_form.

So my question is, should I be manipulating the data via the changeset, using fetch_field and put_change and the like rather than directly manipulating struct? Or should I apply the changeset up front, manipulate the struct it gives me, and then convert it back to a changeset at the end?

Note I have some embeds_many in there too, if that complicates matters.

Given the condition of “only once the changeset is valid” it should be fine both ways. If you wouldn’t have that condition you’d be bound to loose state when applying the changeset to a struct modifying and turning the result back into a changeset.