function migrate_slot_usage(node, path, state) {
	const parent = path.at(-2);
	// Bail on custom element slot usage
	if (
		parent?.type !== 'Component' &&
		parent?.type !== 'SvelteComponent' &&
		node.type !== 'Component' &&
		node.type !== 'SvelteComponent'
	) {
		return;
	}

	let snippet_name = 'children';
	let snippet_props = [];

	// if we stop the transform because the name is not correct we don't want to
	// remove the let directive and they could come before the name
	let removal_queue = [];

	for (let attribute of node.attributes) {
		if (
			attribute.type === 'Attribute' &&
			attribute.name === 'slot' &&
			is_text_attribute(attribute)
		) {
			snippet_name = attribute.value[0].data;
			// the default slot in svelte 4 if what the children slot is for svelte 5
			if (snippet_name === 'default') {
				snippet_name = 'children';
			}
			if (!regex_is_valid_identifier.test(snippet_name) || is_reserved(snippet_name)) {
				has_migration_task = true;
				state.str.appendLeft(
					node.start,
					`<!-- @migration-task: migrate this slot by hand, \`${snippet_name}\` is an invalid identifier -->\n${state.indent}`
				);
				return;
			}
			if (parent?.type === 'Component' || parent?.type === 'SvelteComponent') {
				for (let attribute of parent.attributes) {
					if (attribute.type === 'Attribute' || attribute.type === 'BindDirective') {
						if (attribute.name === snippet_name) {
							state.str.appendLeft(
								node.start,
								`<!-- @migration-task: migrate this slot by hand, \`${snippet_name}\` would shadow a prop on the parent component -->\n${state.indent}`
							);
							return;
						}
					}
				}
			}
			// flush the queue after we found the name
			for (let remove_let of removal_queue) {
				remove_let();
			}
			state.str.remove(attribute.start, attribute.end);
		}
		if (attribute.type === 'LetDirective') {
			snippet_props.push(
				attribute.name +
					(attribute.expression
						? `: ${state.str.original.substring(/** @type {number} */ (attribute.expression.start), /** @type {number} */ (attribute.expression.end))}`
						: '')
			);
			// we just add to the queue to remove them after we found if we need to migrate or we bail
			removal_queue.push(() => state.str.remove(attribute.start, attribute.end));
		}
	}

	if (removal_queue.length > 0) {
		for (let remove_let of removal_queue) {
			remove_let();
		}
	}

	if (node.type === 'SvelteFragment' && node.fragment.nodes.length > 0) {
		// remove node itself, keep content
		state.str.remove(node.start, node.fragment.nodes[0].start);
		state.str.remove(node.fragment.nodes[node.fragment.nodes.length - 1].end, node.end);
	}

	const props = snippet_props.length > 0 ? `{ ${snippet_props.join(', ')} }` : '';

	if (snippet_name === 'children' && node.type !== 'SvelteFragment') {
		if (snippet_props.length === 0) return; // nothing to do

		let inner_start = 0;
		let inner_end = 0;
		for (let i = 0; i < node.fragment.nodes.length; i++) {
			const inner = node.fragment.nodes[i];
			const is_empty_text = inner.type === 'Text' && !inner.data.trim();

			if (
				(inner.type === 'RegularElement' ||
					inner.type === 'SvelteElement' ||
					inner.type === 'Component' ||
					inner.type === 'SvelteComponent' ||
					inner.type === 'SlotElement' ||
					inner.type === 'SvelteFragment') &&
				inner.attributes.some((attr) => attr.type === 'Attribute' && attr.name === 'slot')
			) {
				if (inner_start && !inner_end) {
					// End of default slot content
					inner_end = inner.start;
				}
			} else if (!inner_start && !is_empty_text) {
				// Start of default slot content
				inner_start = inner.start;
			} else if (inner_end && !is_empty_text) {
				// There was default slot content before, then some named slot content, now some default slot content again.
				// We're moving the last character back by one to avoid the closing {/snippet} tag inserted afterwards
				// to come before the opening {#snippet} tag of the named slot.
				state.str.update(inner_end - 1, inner_end, '');
				state.str.prependLeft(inner_end - 1, state.str.original[inner_end - 1]);
				state.str.move(inner.start, inner.end, inner_end - 1);
			}
		}

		if (!inner_end) {
			inner_end = node.fragment.nodes[node.fragment.nodes.length - 1].end;
		}

		state.str.appendLeft(
			inner_start,
			`{#snippet ${snippet_name}(${props})}\n${state.indent.repeat(path.length)}`
		);
		state.str.indent(state.indent, {
			exclude: [
				[0, inner_start],
				[inner_end, state.str.original.length]
			]
		});
		if (inner_end < node.fragment.nodes[node.fragment.nodes.length - 1].end) {
			// Named slots coming afterwards
			state.str.prependLeft(inner_end, `{/snippet}\n${state.indent.repeat(path.length)}`);
		} else {
			// No named slots coming afterwards
			state.str.prependLeft(
				inner_end,
				`${state.indent.repeat(path.length)}{/snippet}\n${state.indent.repeat(path.length - 1)}`
			);
		}
	} else {
		// Named slot or `svelte:fragment`: wrap element itself in a snippet
		state.str.prependLeft(
			node.start,
			`{#snippet ${snippet_name}(${props})}\n${state.indent.repeat(path.length - 2)}`
		);
		state.str.indent(state.indent, {
			exclude: [
				[0, node.start],
				[node.end, state.str.original.length]
			]
		});
		const str = `\n${state.indent.repeat(path.length - 2)}{/snippet}`;

		if (node.type === 'SlotElement') {
			state.str.appendRight(node.end, str);
		} else {
			state.str.appendLeft(node.end, str);
		}
	}
}
@param {AST.RegularElement | AST.SvelteElement | AST.SvelteComponent | AST.Component | AST.SlotElement | AST.SvelteFragment} node
@param {AST.SvelteNode[]} path
@param {State} state