// convertWithVisitorHelper converts HTML with visitor support.
// Called by Convert() when options.Visitor is not nil.
// Returns the ConversionResult or an error.
func convertWithVisitorHelper(html string, options *ConversionOptions, visitor Visitor) (*ConversionResult, error) {
	cHTML := C.CString(html)
	defer C.free(unsafe.Pointer(cHTML))

	var cOptions *C.{{ conversion_options_type }}
	if options != nil {
		optionsBytes, err := json.Marshal(options)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal options: %w", err)
		}
		optionsJSON := C.CString(string(optionsBytes))
		defer C.free(unsafe.Pointer(optionsJSON))
		cOptions = C.{{ fn_options_from_json }}(optionsJSON)
		if cOptions == nil {
			if err := lastError(); err != nil {
				return nil, err
			}
			return nil, fmt.Errorf("failed to decode conversion options")
		}
		defer C.{{ fn_options_free }}(cOptions)
	}
	if cOptions == nil {
		// Allocate a default options struct so we can attach the visitor.
		defaultJSON := C.CString("{}")
		cOptions = C.{{ fn_options_from_json }}(defaultJSON)
		C.free(unsafe.Pointer(defaultJSON))
		if cOptions == nil {
			if err := lastError(); err != nil {
				return nil, err
			}
			return nil, fmt.Errorf("failed to create default conversion options")
		}
		defer C.{{ fn_options_free }}(cOptions)
	}

	// Register visitor and build the C VTable via the static C helper.
	id := registerVisitor(visitor)
	defer unregisterVisitor(id)
	vtbl := C.makeVisitorVTable()

	// Create a bridge that holds the VTable and the visitor ID as user_data.
	bridge := C.{{ fn_bridge_new }}(&vtbl, unsafe.Pointer(id))
	if bridge == nil {
		return nil, fmt.Errorf("failed to create visitor bridge")
	}
	defer C.{{ fn_bridge_free }}(bridge)

	// Attach the bridge to the options struct so convert() picks it up.
	C.{{ fn_options_set_visitor }}(cOptions, (*C.{{ bridge_c_type }})(bridge))

	ptr := C.{{ fn_convert }}(cHTML, cOptions)
	if ptr == nil {
		if err := lastError(); err != nil {
			return nil, err
		}
		return nil, fmt.Errorf("conversion returned nil")
	}
	defer C.{{ fn_result_free }}(ptr)

	jsonPtr := C.{{ fn_result_to_json }}(ptr)
	if jsonPtr == nil {
		return nil, fmt.Errorf("conversion result serialisation failed")
	}
	defer C.free(unsafe.Pointer(jsonPtr))

	var result ConversionResult
	if err := json.Unmarshal([]byte(C.GoString(jsonPtr)), &result); err != nil {
		return nil, fmt.Errorf("failed to decode conversion result: %w", err)
	}
	return &result, nil
}
